diff --git a/src/components/queue/job/JobGroupsList.test.ts b/src/components/queue/job/JobGroupsList.test.ts
index b5679f2764c..f4fcda8538b 100644
--- a/src/components/queue/job/JobGroupsList.test.ts
+++ b/src/components/queue/job/JobGroupsList.test.ts
@@ -4,6 +4,7 @@ import { defineComponent, nextTick } from 'vue'
import JobGroupsList from '@/components/queue/job/JobGroupsList.vue'
import type { JobGroup, JobListItem } from '@/composables/queue/useJobList'
+import type { TaskItemImpl } from '@/stores/queueStore'
const QueueJobItemStub = defineComponent({
name: 'QueueJobItemStub',
@@ -25,20 +26,25 @@ const QueueJobItemStub = defineComponent({
template: '
'
})
-const createJobItem = (overrides: Partial = {}): JobListItem => ({
- id: 'job-id',
- title: 'Example job',
- meta: 'Meta text',
- state: 'running',
- iconName: 'icon',
- iconImageUrl: 'https://example.com/icon.png',
- showClear: true,
- taskRef: { workflow: { id: 'workflow-id' } },
- progressTotalPercent: 60,
- progressCurrentPercent: 30,
- runningNodeName: 'Node A',
- ...overrides
-})
+const createJobItem = (overrides: Partial = {}): JobListItem => {
+ const { taskRef, ...rest } = overrides
+ return {
+ id: 'job-id',
+ title: 'Example job',
+ meta: 'Meta text',
+ state: 'running',
+ iconName: 'icon',
+ iconImageUrl: 'https://example.com/icon.png',
+ showClear: true,
+ taskRef: (taskRef ?? {
+ workflow: { id: 'workflow-id' }
+ }) as TaskItemImpl,
+ progressTotalPercent: 60,
+ progressCurrentPercent: 30,
+ runningNodeName: 'Node A',
+ ...rest
+ }
+}
const mountComponent = (groups: JobGroup[]) =>
mount(JobGroupsList, {
diff --git a/src/composables/graph/useImageMenuOptions.ts b/src/composables/graph/useImageMenuOptions.ts
index 3956edefa4e..0eddf3d00ed 100644
--- a/src/composables/graph/useImageMenuOptions.ts
+++ b/src/composables/graph/useImageMenuOptions.ts
@@ -1,6 +1,7 @@
import { useI18n } from 'vue-i18n'
import { downloadFile } from '@/base/common/downloadUtil'
+import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { useCommandStore } from '@/stores/commandStore'
import type { MenuOption } from './useMoreOptionsMenu'
@@ -16,7 +17,7 @@ export function useImageMenuOptions() {
void commandStore.execute('Comfy.MaskEditor.OpenMaskEditor')
}
- const openImage = (node: any) => {
+ const openImage = (node: LGraphNode) => {
if (!node?.imgs?.length) return
const img = node.imgs[node.imageIndex ?? 0]
if (!img) return
@@ -25,7 +26,7 @@ export function useImageMenuOptions() {
window.open(url.toString(), '_blank')
}
- const copyImage = async (node: any) => {
+ const copyImage = async (node: LGraphNode) => {
if (!node?.imgs?.length) return
const img = node.imgs[node.imageIndex ?? 0]
if (!img) return
@@ -62,7 +63,7 @@ export function useImageMenuOptions() {
}
}
- const saveImage = (node: any) => {
+ const saveImage = (node: LGraphNode) => {
if (!node?.imgs?.length) return
const img = node.imgs[node.imageIndex ?? 0]
if (!img) return
@@ -76,7 +77,7 @@ export function useImageMenuOptions() {
}
}
- const getImageMenuOptions = (node: any): MenuOption[] => {
+ const getImageMenuOptions = (node: LGraphNode): MenuOption[] => {
if (!node?.imgs?.length) return []
return [
diff --git a/src/composables/maskeditor/useBrushDrawing.ts b/src/composables/maskeditor/useBrushDrawing.ts
index a86dcec8570..f1ea0fb38cd 100644
--- a/src/composables/maskeditor/useBrushDrawing.ts
+++ b/src/composables/maskeditor/useBrushDrawing.ts
@@ -404,8 +404,9 @@ export function useBrushDrawing(initialSettings?: {
device = root.device
console.warn('✅ TypeGPU initialized! Root:', root)
console.warn('Device info:', root.device.limits)
- } catch (error: any) {
- console.warn('Failed to initialize TypeGPU:', error.message)
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error)
+ console.warn('Failed to initialize TypeGPU:', message)
}
}
diff --git a/src/composables/queue/useCompletionSummary.ts b/src/composables/queue/useCompletionSummary.ts
index ea240c430d3..4964664a209 100644
--- a/src/composables/queue/useCompletionSummary.ts
+++ b/src/composables/queue/useCompletionSummary.ts
@@ -57,8 +57,8 @@ export const useCompletionSummary = () => {
}
if (prev && !active) {
const start = lastActiveStartTs.value ?? 0
- const finished = queueStore.historyTasks.filter((t: any) => {
- const ts: number | undefined = t.executionEndTimestamp
+ const finished = queueStore.historyTasks.filter((t) => {
+ const ts = t.executionEndTimestamp
return typeof ts === 'number' && ts >= start
})
diff --git a/src/composables/queue/useJobList.ts b/src/composables/queue/useJobList.ts
index 47996636be7..1acac786d1d 100644
--- a/src/composables/queue/useJobList.ts
+++ b/src/composables/queue/useJobList.ts
@@ -38,7 +38,7 @@ export type JobListItem = {
iconName?: string
iconImageUrl?: string
showClear?: boolean
- taskRef?: any
+ taskRef?: TaskItemImpl
progressTotalPercent?: number
progressCurrentPercent?: number
runningNodeName?: string
diff --git a/src/composables/queue/useJobMenu.test.ts b/src/composables/queue/useJobMenu.test.ts
index ff4100918f0..0d8a4359d4b 100644
--- a/src/composables/queue/useJobMenu.test.ts
+++ b/src/composables/queue/useJobMenu.test.ts
@@ -117,13 +117,24 @@ vi.mock('@/utils/formatUtil', () => ({
}))
import { useJobMenu } from '@/composables/queue/useJobMenu'
+import type { TaskItemImpl } from '@/stores/queueStore'
-const createJobItem = (overrides: Partial = {}): JobListItem => ({
+type MockTaskRef = Record
+
+type TestJobListItem = Omit & {
+ taskRef?: MockTaskRef
+}
+
+const createJobItem = (
+ overrides: Partial = {}
+): JobListItem => ({
id: overrides.id ?? 'job-1',
title: overrides.title ?? 'Test job',
meta: overrides.meta ?? 'meta',
state: overrides.state ?? 'completed',
- taskRef: overrides.taskRef,
+ taskRef: overrides.taskRef as Partial | undefined as
+ | TaskItemImpl
+ | undefined,
iconName: overrides.iconName,
iconImageUrl: overrides.iconImageUrl,
showClear: overrides.showClear,
diff --git a/src/composables/queue/useJobMenu.ts b/src/composables/queue/useJobMenu.ts
index 38a37276ff4..6392e22eb2c 100644
--- a/src/composables/queue/useJobMenu.ts
+++ b/src/composables/queue/useJobMenu.ts
@@ -12,7 +12,8 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
import type {
ExecutionErrorWsMessage,
ResultItem,
- ResultItemType
+ ResultItemType,
+ TaskStatus
} from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { downloadBlob } from '@/scripts/utils'
@@ -82,13 +83,20 @@ export function useJobMenu(
await queueStore.update()
}
+ const findExecutionError = (
+ messages: TaskStatus['messages'] | undefined
+ ): ExecutionErrorWsMessage | undefined => {
+ const errMessage = messages?.find((m) => m[0] === 'execution_error')
+ if (errMessage && errMessage[0] === 'execution_error') {
+ return errMessage[1]
+ }
+ return undefined
+ }
+
const copyErrorMessage = async (item?: JobListItem | null) => {
const target = resolveItem(item)
if (!target) return
- const msgs = target.taskRef?.status?.messages as any[] | undefined
- const err = msgs?.find((m: any) => m?.[0] === 'execution_error')?.[1] as
- | ExecutionErrorWsMessage
- | undefined
+ const err = findExecutionError(target.taskRef?.status?.messages)
const message = err?.exception_message
if (message) await copyToClipboard(String(message))
}
@@ -96,10 +104,7 @@ export function useJobMenu(
const reportError = (item?: JobListItem | null) => {
const target = resolveItem(item)
if (!target) return
- const msgs = target.taskRef?.status?.messages as any[] | undefined
- const err = msgs?.find((m: any) => m?.[0] === 'execution_error')?.[1] as
- | ExecutionErrorWsMessage
- | undefined
+ const err = findExecutionError(target.taskRef?.status?.messages)
if (err) useDialogService().showExecutionErrorDialog(err)
}
diff --git a/src/composables/queue/useResultGallery.ts b/src/composables/queue/useResultGallery.ts
index 0e9934165c4..e86ff54d45b 100644
--- a/src/composables/queue/useResultGallery.ts
+++ b/src/composables/queue/useResultGallery.ts
@@ -1,17 +1,29 @@
import { ref, shallowRef } from 'vue'
import type { JobListItem } from '@/composables/queue/useJobList'
-import type { ResultItemImpl } from '@/stores/queueStore'
+
+/** Minimal preview item interface for gallery filtering. */
+interface PreviewItem {
+ url: string
+ supportsPreview: boolean
+}
+
+/** Minimal task interface for gallery preview. */
+interface TaskWithPreview {
+ previewOutput?: T
+}
/**
* Manages result gallery state and activation for queue items.
*/
-export function useResultGallery(getFilteredTasks: () => any[]) {
+export function useResultGallery(
+ getFilteredTasks: () => TaskWithPreview[]
+) {
const galleryActiveIndex = ref(-1)
- const galleryItems = shallowRef([])
+ const galleryItems = shallowRef([])
const onViewItem = (item: JobListItem) => {
- const items: ResultItemImpl[] = getFilteredTasks().flatMap((t: any) => {
+ const items: T[] = getFilteredTasks().flatMap((t) => {
const preview = t.previewOutput
return preview && preview.supportsPreview ? [preview] : []
})
diff --git a/src/composables/useCivitaiModel.ts b/src/composables/useCivitaiModel.ts
index c47b274831f..be15f828c78 100644
--- a/src/composables/useCivitaiModel.ts
+++ b/src/composables/useCivitaiModel.ts
@@ -36,7 +36,7 @@ interface CivitaiModelVersionResponse {
model: CivitaiModel
modelId: number
files: CivitaiModelFile[]
- [key: string]: any
+ [key: string]: unknown
}
/**
diff --git a/src/composables/useContextMenuTranslation.ts b/src/composables/useContextMenuTranslation.ts
index a85cccdc07c..7b031d7ee27 100644
--- a/src/composables/useContextMenuTranslation.ts
+++ b/src/composables/useContextMenuTranslation.ts
@@ -123,7 +123,10 @@ export const useContextMenuTranslation = () => {
}
// for capture translation text of input and widget
- const extraInfo: any = options.extra || options.parentMenu?.options?.extra
+ const extraInfo = (options.extra ||
+ options.parentMenu?.options?.extra) as
+ | { inputs?: INodeInputSlot[]; widgets?: IWidget[] }
+ | undefined
// widgets and inputs
const matchInput = value.content?.match(reInput)
if (matchInput) {
diff --git a/src/composables/useLoad3dViewer.ts b/src/composables/useLoad3dViewer.ts
index 9022c2d2d0d..38b4a9f54ea 100644
--- a/src/composables/useLoad3dViewer.ts
+++ b/src/composables/useLoad3dViewer.ts
@@ -5,9 +5,13 @@ import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import type {
AnimationItem,
BackgroundRenderModeType,
+ CameraConfig,
CameraState,
CameraType,
+ LightConfig,
MaterialMode,
+ ModelConfig,
+ SceneConfig,
UpDirection
} from '@/extensions/core/load3d/interfaces'
import { t } from '@/i18n'
@@ -271,10 +275,18 @@ export const useLoad3dViewer = (node?: LGraphNode) => {
const sourceCameraState = source.getCameraState()
- const sceneConfig = node.properties['Scene Config'] as any
- const modelConfig = node.properties['Model Config'] as any
- const cameraConfig = node.properties['Camera Config'] as any
- const lightConfig = node.properties['Light Config'] as any
+ const sceneConfig = node.properties['Scene Config'] as
+ | SceneConfig
+ | undefined
+ const modelConfig = node.properties['Model Config'] as
+ | ModelConfig
+ | undefined
+ const cameraConfig = node.properties['Camera Config'] as
+ | CameraConfig
+ | undefined
+ const lightConfig = node.properties['Light Config'] as
+ | LightConfig
+ | undefined
isPreview.value = node.type === 'Preview3D'
@@ -438,7 +450,9 @@ export const useLoad3dViewer = (node?: LGraphNode) => {
materialMode: initialState.value.materialMode
}
- const currentCameraConfig = nodeValue.properties['Camera Config'] as any
+ const currentCameraConfig = nodeValue.properties['Camera Config'] as
+ | CameraConfig
+ | undefined
nodeValue.properties['Camera Config'] = {
...currentCameraConfig,
state: initialState.value.cameraState
diff --git a/src/extensions/core/imageCompare.ts b/src/extensions/core/imageCompare.ts
index 608590f1bcb..4821ec062a0 100644
--- a/src/extensions/core/imageCompare.ts
+++ b/src/extensions/core/imageCompare.ts
@@ -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[]
+ b_images?: Record[]
+}>
+
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).a_images as
- | Record[]
- | undefined
- const bImages = (output as Record).b_images as
- | Record[]
- | undefined
+ const { a_images: aImages, b_images: bImages } = output
const rand = app.getRandParam()
const beforeUrl =
diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts
index 668a1fd153b..455776744d6 100644
--- a/src/extensions/core/load3d.ts
+++ b/src/extensions/core/load3d.ts
@@ -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).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('\\', '/')
diff --git a/src/extensions/core/saveMesh.ts b/src/extensions/core/saveMesh.ts
index 9fb33ef94cd..ae94a86093d 100644
--- a/src/extensions/core/saveMesh.ts
+++ b/src/extensions/core/saveMesh.ts
@@ -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 = output['3d']?.[0]
- const fileInfo = message['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)
}
})
}
diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts
index 71c2295d106..fbe6458594d 100644
--- a/src/extensions/core/uploadAudio.ts
+++ b/src/extensions/core/uploadAudio.ts
@@ -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) {
+ onNodeOutputsUpdated(
+ nodeOutputs: Record
+ ) {
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
- 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
+ const audio = output.audio[0]
+ audioUIWidget.element.src = api.apiURL(
+ getResourceURL(audio.subfolder ?? '', audio.filename ?? '', audio.type)
+ )
+ audioUIWidget.element.classList.remove('empty-audio-widget')
}
}
})
diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts
index 0c2d4193e08..1d2d5e80bfa 100644
--- a/src/schemas/apiSchema.ts
+++ b/src/schemas/apiSchema.ts
@@ -35,6 +35,9 @@ const zOutputs = z
export type NodeExecutionOutput = z.infer
+export type NodeOutputWith> =
+ NodeExecutionOutput & T
+
// WS messages
const zStatusWsMessageStatus = z.object({
exec_info: z.object({