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({