-
Notifications
You must be signed in to change notification settings - Fork 648
Feat: update the load 3d widget #7842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
813b16a
4a336fa
f75cd21
2afd363
d7893c1
af8fdc4
328ed07
93fff3a
f47ddb0
ca48b27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,6 +39,27 @@ class Load3DConfiguration { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setting.loadFolder, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setting.cameraState | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (setting.modelWidget.options?.values) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let values = setting.modelWidget.options.values as string[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Extract magic constants for maintainability. The hard-coded localStorage key 🔎 Proposed refactorAdd constants at the top of the class: class Load3DConfiguration {
private static readonly HIDDEN_FILES_STORAGE_KEY = 'Comfy.Load3D.HiddenFiles'
private static readonly MAX_MESH_OPTIONS = 12
// ... rest of the classThen use them in the configure method: try {
- const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
+ const stored = localStorage.getItem(Load3DConfiguration.HIDDEN_FILES_STORAGE_KEY)
const hiddenFiles = stored ? JSON.parse(stored) : []
if (hiddenFiles.length > 0) {
values = values.filter((v) => !hiddenFiles.includes(v))
}
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
}
- if (values.length > 12) {
- values = values.slice(0, 12)
+ if (values.length > Load3DConfiguration.MAX_MESH_OPTIONS) {
+ values = values.slice(0, Load3DConfiguration.MAX_MESH_OPTIONS)
}Also applies to: 56-56 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hiddenFiles = stored ? JSON.parse(stored) : [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (hiddenFiles.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values = values.filter((v) => !hiddenFiles.includes(v)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Failed to read hidden files from localStorage', e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (values.length > 12) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values = values.slice(0, 12) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setting.modelWidget.options.values = values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add type guard before casting to The code casts 🔎 Proposed fix with type guard if (setting.meshWidget.options?.values) {
- let values = setting.meshWidget.options.values as string[]
+ const rawValues = setting.meshWidget.options.values
+ if (!Array.isArray(rawValues)) {
+ return
+ }
+ let values = rawValues.filter((v): v is string => typeof v === 'string')📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents🧹 Nitpick | 🔵 Trivial Consider using immutable operations for filtering and slicing. The current implementation mutates the 🔎 Proposed refactor using immutability if (setting.meshWidget.options?.values) {
- let values = setting.meshWidget.options.values as string[]
+ const rawValues = setting.meshWidget.options.values
+ if (!Array.isArray(rawValues)) {
+ return
+ }
+
+ const validValues = rawValues.filter((v): v is string => typeof v === 'string')
try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
- if (hiddenFiles.length > 0) {
- values = values.filter((v) => !hiddenFiles.includes(v))
- }
+ const filteredValues = hiddenFiles.length > 0
+ ? validValues.filter((v) => !hiddenFiles.includes(v))
+ : validValues
+
+ const limitedValues = filteredValues.length > 12
+ ? filteredValues.slice(0, 12)
+ : filteredValues
+
+ setting.meshWidget.options.values = limitedValues
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
+ setting.meshWidget.options.values = validValues.slice(0, 12)
}
-
- if (values.length > 12) {
- values = values.slice(0, 12)
- }
-
- setting.meshWidget.options.values = values
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.setupTargetSize(setting.width, setting.height) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.setupDefaultProperties(setting.bgImagePath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||||||||||||||||
| :allow-upload="allowUpload" | ||||||||||||||||||||||||||||||
| :upload-folder="uploadFolder" | ||||||||||||||||||||||||||||||
| :is-asset-mode="isAssetMode" | ||||||||||||||||||||||||||||||
| :upload-subfolder="uploadSubfolder" | ||||||||||||||||||||||||||||||
| :default-layout-mode="defaultLayoutMode" | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| <WidgetWithControl | ||||||||||||||||||||||||||||||
|
|
@@ -58,9 +59,13 @@ const specDescriptor = computed<{ | |||||||||||||||||||||||||||||
| kind: AssetKind | ||||||||||||||||||||||||||||||
| allowUpload: boolean | ||||||||||||||||||||||||||||||
| folder: ResultItemType | undefined | ||||||||||||||||||||||||||||||
| subfolder?: string | ||||||||||||||||||||||||||||||
| }>(() => { | ||||||||||||||||||||||||||||||
| const isLoad3DModel = | ||||||||||||||||||||||||||||||
| props.nodeType === 'Load3D' && props.widget.name === 'model_file' | ||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider extracting magic strings to constants. The hardcoded 🔎 Suggested improvement+const LOAD_3D_NODE_TYPE = 'Load3D'
+const MODEL_FILE_WIDGET_NAME = 'model_file'
+
const specDescriptor = computed<{
kind: AssetKind
allowUpload: boolean
folder: ResultItemType | undefined
subfolder?: string
}>(() => {
const isLoad3DMesh =
- props.nodeType === 'Load3D' && props.widget.name === 'model_file'
+ props.nodeType === LOAD_3D_NODE_TYPE && props.widget.name === MODEL_FILE_WIDGET_NAME📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const spec = comboSpec.value | ||||||||||||||||||||||||||||||
| if (!spec) { | ||||||||||||||||||||||||||||||
| if (!spec && !isLoad3DModel) { | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider extracting magic strings to constants. The hardcoded 🔎 Suggested improvement+const LOAD_3D_NODE_TYPE = 'Load3D'
+const MODEL_FILE_WIDGET_NAME = 'model_file'
+
const specDescriptor = computed<{
kind: AssetKind
allowUpload: boolean
folder: ResultItemType | undefined
subfolder?: string
}>(() => {
const isLoad3DModel =
- props.nodeType === 'Load3D' && props.widget.name === 'model_file'
+ props.nodeType === LOAD_3D_NODE_TYPE && props.widget.name === MODEL_FILE_WIDGET_NAME
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| kind: 'unknown', | ||||||||||||||||||||||||||||||
| allowUpload: false, | ||||||||||||||||||||||||||||||
|
|
@@ -74,7 +79,7 @@ const specDescriptor = computed<{ | |||||||||||||||||||||||||||||
| video_upload, | ||||||||||||||||||||||||||||||
| image_folder, | ||||||||||||||||||||||||||||||
| audio_upload | ||||||||||||||||||||||||||||||
| } = spec | ||||||||||||||||||||||||||||||
| } = spec || {} | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let kind: AssetKind = 'unknown' | ||||||||||||||||||||||||||||||
| if (video_upload) { | ||||||||||||||||||||||||||||||
|
|
@@ -83,18 +88,25 @@ const specDescriptor = computed<{ | |||||||||||||||||||||||||||||
| kind = 'image' | ||||||||||||||||||||||||||||||
| } else if (audio_upload) { | ||||||||||||||||||||||||||||||
| kind = 'audio' | ||||||||||||||||||||||||||||||
| } else if (isLoad3DModel) { | ||||||||||||||||||||||||||||||
| kind = 'model' | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| // TODO: add support for models (checkpoints, VAE, LoRAs, etc.) -- get widgetType from spec | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const allowUpload = | ||||||||||||||||||||||||||||||
| image_upload === true || | ||||||||||||||||||||||||||||||
| animated_image_upload === true || | ||||||||||||||||||||||||||||||
| video_upload === true || | ||||||||||||||||||||||||||||||
| audio_upload === true | ||||||||||||||||||||||||||||||
| audio_upload === true || | ||||||||||||||||||||||||||||||
| isLoad3DModel | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const subfolder = isLoad3DModel ? '3d' : undefined | ||||||||||||||||||||||||||||||
| const folder = isLoad3DModel ? 'input' : image_folder | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| kind, | ||||||||||||||||||||||||||||||
| allowUpload, | ||||||||||||||||||||||||||||||
| folder: image_folder | ||||||||||||||||||||||||||||||
| folder, | ||||||||||||||||||||||||||||||
| subfolder | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -120,6 +132,7 @@ const allowUpload = computed(() => specDescriptor.value.allowUpload) | |||||||||||||||||||||||||||||
| const uploadFolder = computed<ResultItemType>(() => { | ||||||||||||||||||||||||||||||
| return specDescriptor.value.folder ?? 'input' | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| const uploadSubfolder = computed(() => specDescriptor.value.subfolder) | ||||||||||||||||||||||||||||||
| const defaultLayoutMode = computed<LayoutMode>(() => { | ||||||||||||||||||||||||||||||
| return isAssetMode.value ? 'list' : 'grid' | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,7 @@ interface Props { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assetKind?: AssetKind | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allowUpload?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadFolder?: ResultItemType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadSubfolder?: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isAssetMode?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultLayoutMode?: LayoutMode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -82,10 +83,6 @@ const filterOptions = computed<FilterOption[]>(() => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selectedSet = ref<Set<SelectedKey>>(new Set()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Transforms a value using getOptionLabel if available. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Falls back to the original value if getOptionLabel is not provided or throws an error. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function getDisplayLabel(value: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const getOptionLabel = props.widget.options?.getOptionLabel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!getOptionLabel) return value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -118,7 +115,6 @@ const outputItems = computed<DropdownItem[]>(() => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const outputs = new Set<string>() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Extract output images/videos from queue history | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queueStore.historyTasks.forEach((task) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task.flatOutputs.forEach((output) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isTargetType = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -129,7 +125,6 @@ const outputItems = computed<DropdownItem[]>(() => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const path = output.subfolder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? `${output.subfolder}/${output.filename}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : output.filename | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add [output] annotation so the preview component knows the type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const annotatedPath = `${path} [output]` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outputs.add(annotatedPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -197,17 +192,17 @@ const uploadable = computed(() => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const acceptTypes = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Be permissive with accept types because backend uses libraries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // that can handle a wide range of formats | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (props.assetKind) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'image': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'image/*' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'video': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'video/*' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'audio': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'audio/*' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'model': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return '.obj,.stl,.ply,.spz' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for 3D model file extension validation or processing in backend
rg -n '\.(obj|stl|ply|spz|fbx|dae|gltf|glb)' --type-list | head -20
# Search for upload endpoint implementations
rg -n 'upload' --type-list | grep -E '(route|endpoint|def.*upload)' | head -20Repository: Comfy-Org/ComfyUI_frontend Length of output: 849 🏁 Script executed: # Search for Python files related to uploads
fd -e py -type f -path '*upload*'
# Search for file extension validation patterns
rg -n 'accept|extension|format' -t py -A3 -B3 | head -50Repository: Comfy-Org/ComfyUI_frontend Length of output: 240 🏁 Script executed: # Look for backend model configuration or constants
rg -n 'obj|stl|ply|spz' -t py -A2 -B2Repository: Comfy-Org/ComfyUI_frontend Length of output: 1021 🏁 Script executed: # Check the current file to understand the context better
find . -name 'WidgetSelectDropdown.vue' -exec cat -n {} \;Repository: Comfy-Org/ComfyUI_frontend Length of output: 13058 🏁 Script executed: #!/bin/bash
# Search for upload endpoint handlers in backend
find . -type f -name "*.py" | xargs grep -l "upload" | head -10Repository: Comfy-Org/ComfyUI_frontend Length of output: 102 🏁 Script executed: #!/bin/bash
# Search for .spz format references
rg -n "spz" -iRepository: Comfy-Org/ComfyUI_frontend Length of output: 50384 🏁 Script executed: #!/bin/bash
# Search for useNodeImageUpload to understand where this was copied from
fd -e ts -e vue | xargs grep -l "useNodeImageUpload" 2>/dev/nullRepository: Comfy-Org/ComfyUI_frontend Length of output: 266 🏁 Script executed: #!/bin/bash
# Search for model format constants or configurations
rg -n "obj|stl|ply" -A2 -B2Repository: Comfy-Org/ComfyUI_frontend Length of output: 50386 🏁 Script executed: #!/bin/bash
# Check if there are any other references to acceptTypes or model uploads
rg -n "acceptTypes|assetKind.*model" -A3 -B3Repository: Comfy-Org/ComfyUI_frontend Length of output: 1634 Update the accept types for 3D model uploads to include all supported formats. The widget currently restricts 3D model uploads to Update line 211 to include at least the commonly used formats: Suggested updateOr include the full set for consistency with backend capabilities: 🤖 Prompt for AI Agents
Comment on lines
+211
to
+212
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update accept types to include all supported 3D formats. The current accept types for 🔎 Proposed fix case 'mesh':
- return '.obj,.stl,.ply,.spz'
+ return '.obj,.stl,.ply,.spz,.fbx,.gltf,.glb,.splat,.ksplat'🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined // model or unknown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -247,15 +242,16 @@ function updateSelectedItems(selectedItems: Set<SelectedKey>) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modelValue.value = name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Upload file function (copied from useNodeImageUpload.ts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uploadFile = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file: File, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isPasted: boolean = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formFields: Partial<{ type: ResultItemType }> = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formFields: Partial<{ type: ResultItemType; subfolder: string }> = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = new FormData() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body.append('image', file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPasted) body.append('subfolder', 'pasted') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else if (formFields.subfolder) body.append('subfolder', formFields.subfolder) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (formFields.type) body.append('type', formFields.type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resp = await api.fetchApi('/upload/image', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -270,7 +266,6 @@ const uploadFile = async ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const data = await resp.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update AssetsStore when uploading to input folder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (formFields.type === 'input' || (!formFields.type && !isPasted)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const assetsStore = useAssetsStore() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await assetsStore.updateInputs() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -279,11 +274,11 @@ const uploadFile = async ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data.subfolder ? `${data.subfolder}/${data.name}` : data.name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle multiple file uploads | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uploadFiles = async (files: File[]): Promise<string[]> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const folder = props.uploadFolder ?? 'input' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const subfolder = props.uploadSubfolder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uploadPromises = files.map((file) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadFile(file, false, { type: folder }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadFile(file, false, { type: folder, subfolder }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const results = await Promise.all(uploadPromises) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return results.filter((path): path is string => path !== null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -293,29 +288,31 @@ async function handleFilesUpdate(files: File[]) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!files || files.length === 0) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. Upload files to server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uploadedPaths = await uploadFiles(files) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (uploadedPaths.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toastStore.addAlert('File upload failed') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2. Update widget options to include new files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This simulates what addToComboValues does but for SimplifiedWidget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (props.widget.options?.values) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadedPaths.forEach((path) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const values = props.widget.options!.values as string[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!values.includes(path)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values.push(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const values = props.widget.options.values as string[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadedPaths.reverse().forEach((path) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const existingIndex = values.indexOf(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (existingIndex > -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values.splice(existingIndex, 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values.unshift(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+317
to
324
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic bug: The Trace for
Result: 🔎 Proposed fix to align modelValue with values[0] if (props.widget.options?.values) {
const values = props.widget.options.values as string[]
- // Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
- uploadedPaths.reverse().forEach((path) => {
+ // Add uploaded paths to top, maintaining upload order (first uploaded at top)
+ ;[...uploadedPaths].reverse().forEach((path) => {
// Remove existing duplicates to move them to top
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
// Enforce limit of 12
if (values.length > 12) {
values.splice(12)
}
}
// 3. Update widget value to the first uploaded file
- modelValue.value = uploadedPaths[0]
+ modelValue.value = uploadedPaths[0] // Now correctly matches values[0]By spreading into a new array before reversing, the original Also applies to: 331-331 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (values.length > 12) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values.splice(12) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+314
to
+329
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add type guard and simplify array manipulation. The code has two concerns:
🔎 Proposed fix with type guard and cleaner logic if (props.widget.options?.values) {
- const values = props.widget.options.values as string[]
+ const rawValues = props.widget.options.values
+ if (!Array.isArray(rawValues)) {
+ return
+ }
+
+ const values = rawValues.filter((v): v is string => typeof v === 'string')
+ const uploadedSet = new Set(uploadedPaths)
+ const filtered = values.filter((v) => !uploadedSet.has(v))
+ const updated = [...uploadedPaths, ...filtered].slice(0, 12)
- uploadedPaths.reverse().forEach((path) => {
- const existingIndex = values.indexOf(path)
- if (existingIndex > -1) {
- values.splice(existingIndex, 1)
- }
- values.unshift(path)
- })
-
- if (values.length > 12) {
- values.splice(12)
- }
+ props.widget.options.values = updated
}This approach:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 3. Update widget value to the first uploaded file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modelValue.value = uploadedPaths[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+314
to
332
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Fix type safety and array mutation bugs in upload handling. This code has multiple critical issues identified in previous reviews:
For
🔎 Proposed fix if (props.widget.options?.values) {
- const values = props.widget.options.values as string[]
+ const rawValues = props.widget.options.values
+ if (!Array.isArray(rawValues)) {
+ return
+ }
+
+ const values = rawValues.filter((v): v is string => typeof v === 'string')
+ const uploadedSet = new Set(uploadedPaths)
+ const filtered = values.filter((v) => !uploadedSet.has(v))
+ const updated = [...uploadedPaths, ...filtered].slice(0, 12)
- // Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
- uploadedPaths.reverse().forEach((path) => {
- // Remove existing duplicates to move them to top
- const existingIndex = values.indexOf(path)
- if (existingIndex > -1) {
- values.splice(existingIndex, 1)
- }
- values.unshift(path)
- })
-
- // Enforce limit of 12
- if (values.length > 12) {
- values.splice(12)
- }
+ props.widget.options.values = updated
}
- // 3. Update widget value to the first uploaded file
modelValue.value = uploadedPaths[0]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 4. Trigger callback to notify underlying LiteGraph widget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (props.widget.callback) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| props.widget.callback(uploadedPaths[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,15 +65,13 @@ function handleVideoLoad(event: Event) { | |
| layout === 'list', | ||
| 'flex-row text-left hover:bg-component-node-widget-background-hovered rounded-lg': | ||
| layout === 'list-small', | ||
| // selection | ||
| 'ring-2 ring-component-node-widget-background-highlighted': | ||
| layout === 'list' && selected | ||
| } | ||
| ) | ||
| " | ||
| @click="handleClick" | ||
| > | ||
| <!-- Image --> | ||
| <div | ||
| v-if="layout !== 'list-small'" | ||
| :class=" | ||
|
|
@@ -85,14 +83,12 @@ function handleVideoLoad(event: Event) { | |
| 'min-w-16 max-w-16 rounded-l-lg': layout === 'list', | ||
| 'rounded-sm group-hover/item:scale-108 group-active/item:scale-95': | ||
| layout === 'grid', | ||
| // selection | ||
| 'ring-2 ring-component-node-widget-background-highlighted': | ||
| layout === 'grid' && selected | ||
| } | ||
| ) | ||
| " | ||
| > | ||
| <!-- Selected Icon --> | ||
| <div | ||
| v-if="selected" | ||
| class="absolute top-1 left-1 size-4 rounded-full border-1 border-base-foreground bg-primary-background" | ||
|
|
@@ -118,10 +114,11 @@ function handleVideoLoad(event: Event) { | |
| /> | ||
| <div | ||
| v-else | ||
| class="size-full bg-gradient-to-tr from-blue-400 via-teal-500 to-green-400" | ||
| /> | ||
| class="size-full flex items-center justify-center bg-slate-50" | ||
| > | ||
| <i class="pi pi-box text-slate-400" style="font-size: 2.5rem" /> | ||
| </div> | ||
|
Comment on lines
119
to
+124
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Replace hardcoded colors with semantic theme values. The placeholder uses hardcoded Tailwind colors ( 🔎 Proposed refactor using semantic values <div
v-else
- class="size-full flex items-center justify-center bg-slate-50"
+ class="size-full flex items-center justify-center bg-component-node-widget-background"
>
- <i class="pi pi-box text-slate-400" style="font-size: 2.5rem" />
+ <i class="pi pi-box text-[2.5rem] text-muted-foreground" />
</div>Note: Verify that As per coding guidelines: "Do not use the
🤖 Prompt for AI Agents |
||
| </div> | ||
| <!-- Name --> | ||
| <div | ||
| :class=" | ||
| cn('flex gap-1', { | ||
|
|
@@ -138,14 +135,12 @@ function handleVideoLoad(event: Event) { | |
| cn( | ||
| 'block text-xs line-clamp-2 break-words overflow-hidden', | ||
| 'transition-colors duration-150', | ||
| // selection | ||
| !!selected && 'text-base-foreground' | ||
| ) | ||
| " | ||
| > | ||
| {{ label ?? name }} | ||
| </span> | ||
| <!-- Meta Data --> | ||
| <span class="text-secondary block text-xs">{{ | ||
| metadata || actualDimensions | ||
| }}</span> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add type guard before casting to
string[].The code casts
setting.modelWidget.options.valuestostring[]without verifying that it's actually an array of strings. This could cause runtime errors if the values contain non-string elements.🔎 Proposed fix with type guard
if (setting.modelWidget.options?.values) { - let values = setting.modelWidget.options.values as string[] + const rawValues = setting.modelWidget.options.values + if (!Array.isArray(rawValues)) { + return + } + let values = rawValues.filter((v): v is string => typeof v === 'string')🤖 Prompt for AI Agents