Compare commits

..

1 Commits

Author SHA1 Message Date
Terry Jia
4bde964aed [upstream] Upstream PreviewAny from rgthree-comfy 2025-04-26 07:38:55 -04:00
23 changed files with 67 additions and 336 deletions

View File

@@ -67,20 +67,6 @@ The development of successive minor versions overlaps. For example, while versio
| 3 | Mar 15-21 | Released | Feature Freeze | Development | 1.1.7 through 1.1.13 (daily)<br>1.2.0 through 1.2.6 (daily) |
| 4 | Mar 22-28 | - | Released | Feature Freeze | 1.2.7 through 1.2.13 (daily)<br>1.3.0 through 1.3.6 (daily) |
## Contributing
We're building this frontend together and would love your help — no matter how you'd like to pitch in! You don't need to write code to make a difference.
Here are some ways to get involved:
- **Pull Requests:** Add features, fix bugs, or improve code health. Browse [issues](https://github.com/Comfy-Org/ComfyUI_frontend/issues) for inspiration.
- **Vote on Features:** Give a 👍 to the feature requests you care about to help us prioritize.
- **Verify Bugs:** Try reproducing reported issues and share your results (even if the bug doesn't occur!).
- **Community Support:** Hop into our [Discord](https://www.comfy.org/discord) to answer questions or get help.
- **Share & Advocate:** Tell your friends, tweet about us, or share tips to support the project.
Have another idea? Drop into Discord or open an issue, and let's chat!
## Development
### Tech Stack

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -48,7 +48,6 @@ const customColorPalettes: Record<string, Palette> = {
WIDGET_OUTLINE_COLOR: '#333',
WIDGET_TEXT_COLOR: '#a3a3a8',
WIDGET_SECONDARY_TEXT_COLOR: '#97979c',
WIDGET_DISABLED_TEXT_COLOR: '#646464',
LINK_COLOR: '#9A9',
EVENT_LINK_COLOR: '#A86',
CONNECTING_LINK_COLOR: '#AFA'
@@ -112,7 +111,6 @@ const customColorPalettes: Record<string, Palette> = {
WIDGET_OUTLINE_COLOR: '#333',
WIDGET_TEXT_COLOR: '#a3a3a8',
WIDGET_SECONDARY_TEXT_COLOR: '#97979c',
WIDGET_DISABLED_TEXT_COLOR: '#646464',
LINK_COLOR: '#9A9',
EVENT_LINK_COLOR: '#A86',
CONNECTING_LINK_COLOR: '#AFA'

View File

@@ -9,10 +9,7 @@ test.describe('Load Workflow in Media', () => {
'no_workflow.webp',
'large_workflow.webp',
'workflow.webm',
'workflow.glb',
'workflow.mp4',
'workflow.mov',
'workflow.m4v'
'workflow.glb'
]
fileNames.forEach(async (fileName) => {
test(`Load workflow in ${fileName} (drop from filesystem)`, async ({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

12
package-lock.json generated
View File

@@ -1,18 +1,18 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.18.2",
"version": "1.18.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.18.2",
"version": "1.18.1",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.42",
"@comfyorg/litegraph": "^0.14.2",
"@comfyorg/litegraph": "^0.14.1",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
@@ -482,9 +482,9 @@
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.14.2.tgz",
"integrity": "sha512-IGi1EyWPSRXkmNaFm4WWx0SnfLPc2DDJNVbiTfubDBwGWDRZqsA2vlCYN/1gFgw7evy02XrvcSYYuSUC4+gc+g==",
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.14.1.tgz",
"integrity": "sha512-P/OlMsFHgkubvXrLhEJohQO+1e+xx6X/8yTXYFNj5Uz44GaXObaCDv+5guMSHASzuwiKMv4MPJbWSbdjeg5qVg==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.18.2",
"version": "1.18.1",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -72,7 +72,7 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.42",
"@comfyorg/litegraph": "^0.14.2",
"@comfyorg/litegraph": "^0.14.1",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",

View File

@@ -50,7 +50,6 @@
"WIDGET_OUTLINE_COLOR": "#6e7581",
"WIDGET_TEXT_COLOR": "#DDD",
"WIDGET_SECONDARY_TEXT_COLOR": "#b2b7bd",
"WIDGET_DISABLED_TEXT_COLOR": "#5c626d",
"LINK_COLOR": "#9A9",
"EVENT_LINK_COLOR": "#A86",
"CONNECTING_LINK_COLOR": "#AFA"
@@ -75,4 +74,4 @@
"bar-shadow": "rgba(8, 8, 8, 0.75) 0 0 0.5rem"
}
}
}
}

View File

@@ -42,7 +42,6 @@
"WIDGET_OUTLINE_COLOR": "#666",
"WIDGET_TEXT_COLOR": "#DDD",
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
"WIDGET_DISABLED_TEXT_COLOR": "#666",
"LINK_COLOR": "#9A9",
"EVENT_LINK_COLOR": "#A86",
"CONNECTING_LINK_COLOR": "#AFA",
@@ -69,4 +68,4 @@
"bar-shadow": "rgba(16, 16, 16, 0.5) 0 0 0.5rem"
}
}
}
}

View File

@@ -50,7 +50,6 @@
"WIDGET_OUTLINE_COLOR": "#30363d",
"WIDGET_TEXT_COLOR": "#bcc2c8",
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
"WIDGET_DISABLED_TEXT_COLOR": "#30363d",
"LINK_COLOR": "#9A9",
"EVENT_LINK_COLOR": "#A86",
"CONNECTING_LINK_COLOR": "#AFA"
@@ -75,4 +74,4 @@
"bar-shadow": "rgba(0, 0, 0, 1) 0 0 0.5rem"
}
}
}
}

View File

@@ -43,7 +43,6 @@
"WIDGET_OUTLINE_COLOR": "#999",
"WIDGET_TEXT_COLOR": "#222",
"WIDGET_SECONDARY_TEXT_COLOR": "#555",
"WIDGET_DISABLED_TEXT_COLOR": "#999",
"LINK_COLOR": "#4CAF50",
"EVENT_LINK_COLOR": "#FF9800",
"CONNECTING_LINK_COLOR": "#2196F3",
@@ -71,4 +70,4 @@
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem"
}
}
}
}

View File

@@ -50,7 +50,6 @@
"WIDGET_OUTLINE_COLOR": "#545d70",
"WIDGET_TEXT_COLOR": "#bcc2c8",
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
"WIDGET_DISABLED_TEXT_COLOR": "#545d70",
"LINK_COLOR": "#9A9",
"EVENT_LINK_COLOR": "#A86",
"CONNECTING_LINK_COLOR": "#AFA"
@@ -75,4 +74,4 @@
"bar-shadow": "rgba(0, 0, 0, 0.75) 0 0 0.5rem"
}
}
}
}

View File

@@ -35,7 +35,6 @@
"WIDGET_OUTLINE_COLOR": "#839496",
"WIDGET_TEXT_COLOR": "#fdf6e3",
"WIDGET_SECONDARY_TEXT_COLOR": "#93a1a1",
"WIDGET_DISABLED_TEXT_COLOR": "#657b83",
"LINK_COLOR": "#2aa198",
"EVENT_LINK_COLOR": "#268bd2",
"CONNECTING_LINK_COLOR": "#859900"
@@ -60,4 +59,4 @@
"bar-shadow": "rgba(16, 16, 16, 0.5) 0 0 0.5rem"
}
}
}
}

View File

@@ -10,6 +10,7 @@ import './load3d'
import './maskeditor'
import './nodeTemplates'
import './noteNode'
import './previewAny'
import './rerouteNode'
import './saveImageExtraOutput'
import './saveMesh'

View File

@@ -0,0 +1,50 @@
/*
Preview Any - original implement from
https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py
upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
*/
import { IWidget } from '@comfyorg/litegraph'
import { DOMWidget } from '@/scripts/domWidget'
import { ComfyWidgets } from '@/scripts/widgets'
import { useExtensionService } from '@/services/extensionService'
useExtensionService().registerExtension({
name: 'Comfy.PreviewAny',
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === 'PreviewAny') {
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined
const showValueWidget = ComfyWidgets['STRING'](
this,
'preview',
['STRING', { multiline: true }],
app
).widget as DOMWidget<any, any>
showValueWidget.element.readOnly = true
console.log(showValueWidget)
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted === null || onExecuted === void 0
? void 0
: onExecuted.apply(this, [message])
const previewWidget = this.widgets?.find(
(w: IWidget) => w.name === 'preview'
)
if (previewWidget) {
previewWidget.value = message.text[0]
}
}
}
}
})

View File

@@ -48,7 +48,6 @@ const litegraphBaseSchema = z.object({
WIDGET_OUTLINE_COLOR: z.string(),
WIDGET_TEXT_COLOR: z.string(),
WIDGET_SECONDARY_TEXT_COLOR: z.string(),
WIDGET_DISABLED_TEXT_COLOR: z.string(),
LINK_COLOR: z.string(),
EVENT_LINK_COLOR: z.string(),
CONNECTING_LINK_COLOR: z.string(),

View File

@@ -27,7 +27,6 @@ import {
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
import { getFromWebmFile } from '@/scripts/metadata/ebml'
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
import { useDialogService } from '@/services/dialogService'
import { useExtensionService } from '@/services/extensionService'
import { useLitegraphService } from '@/services/litegraphService'
@@ -1334,20 +1333,6 @@ export class ComfyApp {
} else {
this.showErrorOnFileLoad(file)
}
} else if (
file.type === 'video/mp4' ||
file.name?.endsWith('.mp4') ||
file.name?.endsWith('.mov') ||
file.name?.endsWith('.m4v') ||
file.type === 'video/quicktime' ||
file.type === 'video/x-m4v'
) {
const mp4Info = await getFromIsobmffFile(file)
if (mp4Info.workflow) {
this.loadGraphData(mp4Info.workflow, true, true, fileName)
} else if (mp4Info.prompt) {
this.loadApiJson(mp4Info.prompt, fileName)
}
} else if (
file.type === 'model/gltf-binary' ||
file.name?.endsWith('.glb')

View File

@@ -1,272 +0,0 @@
import {
ComfyApiWorkflow,
ComfyWorkflowJSON
} from '@/schemas/comfyWorkflowSchema'
import {
ASCII,
ComfyMetadata,
ComfyMetadataTags,
IsobmffBoxContentRange
} from '@/types/metadataTypes'
const MAX_READ_BYTES = 2 * 1024 * 1024
const BOX_TYPES = {
USER_DATA: [0x75, 0x64, 0x74, 0x61],
META_DATA: [0x6d, 0x65, 0x74, 0x61],
ITEM_LIST: [0x69, 0x6c, 0x73, 0x74],
KEYS: [0x6b, 0x65, 0x79, 0x73],
DATA: [0x64, 0x61, 0x74, 0x61],
MOVIE: [0x6d, 0x6f, 0x6f, 0x76]
}
const SIZES = {
HEADER: 8,
VERSION: 4,
LOCALE: 4,
ITEM_MIN: 8
}
const bufferMatchesBoxType = (
data: Uint8Array,
pos: number,
boxType: number[]
): boolean => {
if (pos + 4 > data.length) return false
for (let i = 0; i < 4; i++) {
if (data[pos + i] !== boxType[i]) return false
}
return true
}
const readUint32 = (data: Uint8Array, pos: number): number => {
if (pos + 4 > data.length) return 0
return (
(data[pos] << 24) |
(data[pos + 1] << 16) |
(data[pos + 2] << 8) |
data[pos + 3]
)
}
const findIsobmffBoxByType = (
data: Uint8Array,
startPos: number,
endPos: number,
boxType: number[]
): IsobmffBoxContentRange => {
for (let pos = startPos; pos < endPos - 8; pos++) {
const size = readUint32(data, pos)
if (size < SIZES.ITEM_MIN) continue // Minimum size is 8 bytes
if (bufferMatchesBoxType(data, pos + 4, boxType))
return { start: pos + SIZES.HEADER, end: pos + size } // Skip header
// If type doesn't match, ensure size is valid before skipping
if (pos + size > endPos) return null
pos += size - 1 // Skip to the next potential box start
}
return null
}
const extractJson = (data: Uint8Array, start: number, end: number): any => {
let jsonStart = start
while (jsonStart < end && data[jsonStart] !== ASCII.OPEN_BRACE) {
jsonStart++
}
if (jsonStart >= end) return null
try {
const jsonText = new TextDecoder().decode(data.slice(jsonStart, end))
return JSON.parse(jsonText)
} catch {
return null
}
}
const readUtf8String = (data: Uint8Array, start: number, end: number): string =>
new TextDecoder().decode(data.slice(start, end))
const parseKeysBox = (
data: Uint8Array,
keysBoxStart: number,
keysBoxEnd: number
): Map<number, string> => {
const keysMap = new Map<number, string>()
let pos = keysBoxStart + 4 // Skip version/flags
if (pos + 4 > keysBoxEnd) return keysMap
const entryCount = readUint32(data, pos)
pos += 4
for (let i = 1; i <= entryCount; i++) {
// Keys are 1-indexed
if (pos + SIZES.HEADER > keysBoxEnd) break
const keySize = readUint32(data, pos)
pos += SIZES.HEADER
const keyNameEnd = pos + keySize - SIZES.HEADER
if (keySize < SIZES.ITEM_MIN || keyNameEnd > keysBoxEnd) break
const keyName = readUtf8String(data, pos, keyNameEnd)
keysMap.set(i, keyName)
pos = keyNameEnd
}
return keysMap
}
const extractMetadataValueFromDataBox = (
data: Uint8Array,
dataBoxStart: number,
dataBoxEnd: number,
keyName: string
): ComfyWorkflowJSON | ComfyApiWorkflow | null => {
const valueStart = dataBoxStart + SIZES.VERSION + SIZES.LOCALE
if (valueStart >= dataBoxEnd) return null
const lowerKeyName = keyName.toLowerCase()
if (
lowerKeyName === ComfyMetadataTags.PROMPT.toLowerCase() ||
lowerKeyName === ComfyMetadataTags.WORKFLOW.toLowerCase()
) {
return extractJson(data, valueStart, dataBoxEnd) || null
}
return null
}
const parseIlstItem = (
data: Uint8Array,
itemStart: number,
itemEnd: number,
keysMap: Map<number, string>,
metadata: ComfyMetadata
) => {
if (itemStart + SIZES.HEADER > itemEnd) return
const itemIndex = readUint32(data, itemStart + 4)
const keyName = keysMap.get(itemIndex)
if (!keyName) return
const dataBox = findIsobmffBoxByType(
data,
itemStart + SIZES.HEADER,
itemEnd,
BOX_TYPES.DATA
)
if (dataBox) {
const value = extractMetadataValueFromDataBox(
data,
dataBox.start,
dataBox.end,
keyName
)
if (value !== null) {
metadata[keyName.toLowerCase() as keyof ComfyMetadata] = value
}
}
}
const parseIlstBox = (
data: Uint8Array,
ilstStart: number,
ilstEnd: number,
keysMap: Map<number, string>,
metadata: ComfyMetadata
) => {
let pos = ilstStart
while (pos < ilstEnd - SIZES.HEADER) {
const itemSize = readUint32(data, pos)
if (itemSize <= SIZES.HEADER || pos + itemSize > ilstEnd) break // Invalid item size
parseIlstItem(data, pos, pos + itemSize, keysMap, metadata)
pos += itemSize
}
}
const findUserDataBox = (data: Uint8Array): IsobmffBoxContentRange => {
let userDataBox: IsobmffBoxContentRange = null
// Metadata can be in 'udta' at top level or inside 'moov'
userDataBox = findIsobmffBoxByType(data, 0, data.length, BOX_TYPES.USER_DATA)
if (!userDataBox) {
const moovBox = findIsobmffBoxByType(data, 0, data.length, BOX_TYPES.MOVIE)
if (moovBox) {
userDataBox = findIsobmffBoxByType(
data,
moovBox.start,
moovBox.end,
BOX_TYPES.USER_DATA
)
}
}
return userDataBox
}
const parseIsobmffMetadata = (data: Uint8Array): ComfyMetadata => {
const metadata: ComfyMetadata = {}
const userDataBox = findUserDataBox(data)
if (!userDataBox) return metadata
const metaBox = findIsobmffBoxByType(
data,
userDataBox.start,
userDataBox.end,
BOX_TYPES.META_DATA
)
if (!metaBox) return metadata
const metaContentStart = metaBox.start + SIZES.VERSION
const keysBox = findIsobmffBoxByType(
data,
metaContentStart,
metaBox.end,
BOX_TYPES.KEYS
)
if (!keysBox) return metadata
const keysMap = parseKeysBox(data, keysBox.start, keysBox.end)
if (keysMap.size === 0) return metadata // keys box is empty or failed to parse
const ilstBox = findIsobmffBoxByType(
data,
metaContentStart,
metaBox.end,
BOX_TYPES.ITEM_LIST
)
if (!ilstBox) return metadata
parseIlstBox(data, ilstBox.start, ilstBox.end, keysMap, metadata)
return metadata
}
/**
* Extracts ComfyUI Workflow metadata from an ISO Base Media File Format (ISOBMFF) file
* (e.g., MP4, MOV) by parsing the `udta.meta.keys` and `udta.meta.ilst` boxes.
* @param file - The file to extract metadata from.
*/
export function getFromIsobmffFile(file: File): Promise<ComfyMetadata> {
return new Promise<ComfyMetadata>((resolve) => {
const reader = new FileReader()
reader.onload = (event: ProgressEvent<FileReader>) => {
if (!event.target?.result) {
resolve({})
return
}
try {
const data = new Uint8Array(event.target.result as ArrayBuffer)
resolve(parseIsobmffMetadata(data))
} catch (e) {
console.error('Parser: Error parsing ISOBMFF metadata:', e)
resolve({})
}
}
reader.onerror = (err) => {
console.error('FileReader: Error reading ISOBMFF file:', err)
resolve({})
}
reader.readAsArrayBuffer(file.slice(0, MAX_READ_BYTES))
})
}

View File

@@ -46,8 +46,7 @@ export type TextRange = {
export enum ASCII {
GLTF = 0x46546c67,
JSON = 0x4e4f534a,
OPEN_BRACE = 0x7b
JSON = 0x4e4f534a
}
export enum GltfSizeBytes {
@@ -79,9 +78,3 @@ export type GltfJsonData = {
}
[key: string]: any
}
/**
* Represents the content range [start, end) of an ISOBMFF box, excluding its header.
* Null if the box was not found.
*/
export type IsobmffBoxContentRange = { start: number; end: number } | null