mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 18:22:40 +00:00
Road to No Explicit Any Part 6: Composables and Extensions (#8083)
## Summary - Type `onExecuted` callbacks with `NodeExecutionOutput` in saveMesh.ts and uploadAudio.ts - Type composable parameters and return values properly (useLoad3dViewer, useImageMenuOptions, useJobMenu, useResultGallery, useContextMenuTranslation) - Type `taskRef` as `TaskItemImpl` with updated test mocks - Fix error catch and index signature patterns without `any` - Add `NodeOutputWith<T>` generic helper for typed access to passthrough properties on `NodeExecutionOutput` ## Test plan - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] Unit tests pass for affected files - [x] Sourcegraph checks confirm no external usage of modified types ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8083-Road-to-No-Explicit-Any-Part-6-Composables-and-Extensions-2e96d73d3650810fb033d745bf88a22b) by [Unito](https://www.unito.io)
This commit is contained in:
committed by
GitHub
parent
0d5ca96a2b
commit
c56e8425d4
@@ -1,8 +1,13 @@
|
||||
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||
import type { NodeOutputWith } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
|
||||
type ImageCompareOutput = NodeOutputWith<{
|
||||
a_images?: Record<string, string>[]
|
||||
b_images?: Record<string, string>[]
|
||||
}>
|
||||
|
||||
useExtensionService().registerExtension({
|
||||
name: 'Comfy.ImageCompare',
|
||||
|
||||
@@ -14,15 +19,10 @@ useExtensionService().registerExtension({
|
||||
|
||||
const onExecuted = node.onExecuted
|
||||
|
||||
node.onExecuted = function (output: NodeExecutionOutput) {
|
||||
node.onExecuted = function (output: ImageCompareOutput) {
|
||||
onExecuted?.call(this, output)
|
||||
|
||||
const aImages = (output as Record<string, unknown>).a_images as
|
||||
| Record<string, string>[]
|
||||
| undefined
|
||||
const bImages = (output as Record<string, unknown>).b_images as
|
||||
| Record<string, string>[]
|
||||
| undefined
|
||||
const { a_images: aImages, b_images: bImages } = output
|
||||
const rand = app.getRandParam()
|
||||
|
||||
const beforeUrl =
|
||||
|
||||
@@ -15,7 +15,11 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import type { IStringWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||
import type { NodeOutputWith } from '@/schemas/apiSchema'
|
||||
|
||||
type Load3dPreviewOutput = NodeOutputWith<{
|
||||
result?: [string?, CameraState?, string?]
|
||||
}>
|
||||
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { api } from '@/scripts/api'
|
||||
import { ComfyApp, app } from '@/scripts/app'
|
||||
@@ -496,13 +500,11 @@ useExtensionService().registerExtension({
|
||||
config.configure(settings)
|
||||
}
|
||||
|
||||
node.onExecuted = function (output: NodeExecutionOutput) {
|
||||
node.onExecuted = function (output: Load3dPreviewOutput) {
|
||||
onExecuted?.call(this, output)
|
||||
|
||||
const result = (output as Record<string, unknown>).result as
|
||||
| unknown[]
|
||||
| undefined
|
||||
const filePath = result?.[0] as string | undefined
|
||||
const result = output.result
|
||||
const filePath = result?.[0]
|
||||
|
||||
if (!filePath) {
|
||||
const msg = t('toastMessages.unableToGetModelFilePath')
|
||||
@@ -510,8 +512,8 @@ useExtensionService().registerExtension({
|
||||
useToastStore().addAlert(msg)
|
||||
}
|
||||
|
||||
const cameraState = result?.[1] as CameraState | undefined
|
||||
const bgImagePath = result?.[2] as string | undefined
|
||||
const cameraState = result?.[1]
|
||||
const bgImagePath = result?.[2]
|
||||
|
||||
modelWidget.value = filePath?.replaceAll('\\', '/')
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper
|
||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { NodeOutputWith, ResultItem } from '@/schemas/apiSchema'
|
||||
|
||||
type SaveMeshOutput = NodeOutputWith<{
|
||||
'3d'?: ResultItem[]
|
||||
}>
|
||||
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import { useLoad3dService } from '@/services/load3dService'
|
||||
@@ -70,22 +75,26 @@ useExtensionService().registerExtension({
|
||||
|
||||
const onExecuted = node.onExecuted
|
||||
|
||||
node.onExecuted = function (message: any) {
|
||||
onExecuted?.apply(this, arguments as any)
|
||||
node.onExecuted = function (output: SaveMeshOutput) {
|
||||
onExecuted?.call(this, output)
|
||||
|
||||
const fileInfo = message['3d'][0]
|
||||
const fileInfo = output['3d']?.[0]
|
||||
|
||||
if (!fileInfo) return
|
||||
|
||||
useLoad3d(node).waitForLoad3d((load3d) => {
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
|
||||
if (load3d && modelWidget) {
|
||||
const filePath = fileInfo['subfolder'] + '/' + fileInfo['filename']
|
||||
const filePath =
|
||||
(fileInfo.subfolder ?? '') + '/' + (fileInfo.filename ?? '')
|
||||
|
||||
modelWidget.value = filePath
|
||||
|
||||
const config = new Load3DConfiguration(load3d, node.properties)
|
||||
|
||||
config.configureForSaveMesh(fileInfo['type'], filePath)
|
||||
const loadFolder = fileInfo.type as 'input' | 'output'
|
||||
config.configureForSaveMesh(loadFolder, filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getResourceURL,
|
||||
splitFilePath
|
||||
} from '@/renderer/extensions/vueNodes/widgets/utils/audioUtils'
|
||||
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import type { DOMWidget } from '@/scripts/domWidget'
|
||||
import { useAudioService } from '@/services/audioService'
|
||||
@@ -112,14 +113,17 @@ app.registerExtension({
|
||||
audioUIWidget.element.classList.add('empty-audio-widget')
|
||||
// Populate the audio widget UI on node execution.
|
||||
const onExecuted = node.onExecuted
|
||||
node.onExecuted = function (message: any) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
onExecuted?.apply(this, arguments)
|
||||
const audios = message.audio
|
||||
if (!audios) return
|
||||
node.onExecuted = function (output: NodeExecutionOutput) {
|
||||
onExecuted?.call(this, output)
|
||||
const audios = output.audio
|
||||
if (!audios?.length) return
|
||||
const audio = audios[0]
|
||||
audioUIWidget.element.src = api.apiURL(
|
||||
getResourceURL(audio.subfolder, audio.filename, audio.type)
|
||||
getResourceURL(
|
||||
audio.subfolder ?? '',
|
||||
audio.filename ?? '',
|
||||
audio.type
|
||||
)
|
||||
)
|
||||
audioUIWidget.element.classList.remove('empty-audio-widget')
|
||||
}
|
||||
@@ -139,22 +143,23 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
},
|
||||
onNodeOutputsUpdated(nodeOutputs: Record<NodeLocatorId, any>) {
|
||||
onNodeOutputsUpdated(
|
||||
nodeOutputs: Record<NodeLocatorId, NodeExecutionOutput>
|
||||
) {
|
||||
for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) {
|
||||
if ('audio' in output) {
|
||||
const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId)
|
||||
if (!node) continue
|
||||
if (!output.audio?.length) continue
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const audioUIWidget = node.widgets.find(
|
||||
(w) => w.name === 'audioUI'
|
||||
) as unknown as DOMWidget<HTMLAudioElement, string>
|
||||
const audio = output.audio[0]
|
||||
audioUIWidget.element.src = api.apiURL(
|
||||
getResourceURL(audio.subfolder, audio.filename, audio.type)
|
||||
)
|
||||
audioUIWidget.element.classList.remove('empty-audio-widget')
|
||||
}
|
||||
const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId)
|
||||
if (!node) continue
|
||||
|
||||
const audioUIWidget = node.widgets?.find(
|
||||
(w) => w.name === 'audioUI'
|
||||
) as unknown as DOMWidget<HTMLAudioElement, string>
|
||||
const audio = output.audio[0]
|
||||
audioUIWidget.element.src = api.apiURL(
|
||||
getResourceURL(audio.subfolder ?? '', audio.filename ?? '', audio.type)
|
||||
)
|
||||
audioUIWidget.element.classList.remove('empty-audio-widget')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user