Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions src/components/queue/job/JobGroupsList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -25,20 +26,25 @@ const QueueJobItemStub = defineComponent({
template: '<div class="queue-job-item-stub"></div>'
})

const createJobItem = (overrides: Partial<JobListItem> = {}): 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> = {}): 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
}
}
Comment on lines +29 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider explicit partial type acknowledgment for the mock.

The minimal mock object { workflow: { id: 'workflow-id' } } only implements a subset of TaskItemImpl. Per repository learnings, using as Partial<TaskItemImpl> as TaskItemImpl makes the incomplete implementation explicit while maintaining type safety.

🔧 Suggested improvement
-    taskRef: (taskRef ?? {
-      workflow: { id: 'workflow-id' }
-    }) as TaskItemImpl,
+    taskRef: (taskRef ?? {
+      workflow: { id: 'workflow-id' }
+    }) as Partial<TaskItemImpl> as TaskItemImpl,

Based on learnings, this pattern explicitly acknowledges that the mock doesn't fully implement the interface.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createJobItem = (overrides: Partial<JobListItem> = {}): 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 createJobItem = (overrides: Partial<JobListItem> = {}): 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 Partial<TaskItemImpl> as TaskItemImpl,
progressTotalPercent: 60,
progressCurrentPercent: 30,
runningNodeName: 'Node A',
...rest
}
}
🤖 Prompt for AI Agents
In `@src/components/queue/job/JobGroupsList.test.ts` around lines 29 - 47, The
default mock in createJobItem returns an incomplete TaskItemImpl for the taskRef
field; change the fallback to explicitly acknowledge the partial type by casting
the object as Partial<TaskItemImpl> and then to TaskItemImpl (e.g., replace the
current (taskRef ?? { workflow: { id: 'workflow-id' } }) as TaskItemImpl with
(taskRef ?? ({ workflow: { id: 'workflow-id' } } as Partial<TaskItemImpl>)) as
TaskItemImpl) so the mock is explicit while preserving type compatibility for
JobListItem.


const mountComponent = (groups: JobGroup[]) =>
mount(JobGroupsList, {
Expand Down
9 changes: 5 additions & 4 deletions src/composables/graph/useImageMenuOptions.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -76,7 +77,7 @@ export function useImageMenuOptions() {
}
}

const getImageMenuOptions = (node: any): MenuOption[] => {
const getImageMenuOptions = (node: LGraphNode): MenuOption[] => {
if (!node?.imgs?.length) return []

return [
Expand Down
5 changes: 3 additions & 2 deletions src/composables/maskeditor/useBrushDrawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/composables/queue/useCompletionSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})

Expand Down
2 changes: 1 addition & 1 deletion src/composables/queue/useJobList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type JobListItem = {
iconName?: string
iconImageUrl?: string
showClear?: boolean
taskRef?: any
taskRef?: TaskItemImpl
progressTotalPercent?: number
progressCurrentPercent?: number
runningNodeName?: string
Expand Down
15 changes: 13 additions & 2 deletions src/composables/queue/useJobMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = {}): JobListItem => ({
type MockTaskRef = Record<string, unknown>

type TestJobListItem = Omit<JobListItem, 'taskRef'> & {
taskRef?: MockTaskRef
}

const createJobItem = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const createJobItem = (
const createJobItem = (
overrides: Partial<TestJobListItem> = {}
): JobListItem => ({
id: 'job-1',
title: 'test job',
meta: 'meta',
state: 'completed',
...overrides,
taskRef: overrides.taskRef as Partial<TaskItemImpl>|undefined as TaskItemImpl|undefined
})

This one is a good big gnarlier and potentially out of scope for any cleanup

overrides: Partial<TestJobListItem> = {}
): 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<TaskItemImpl> | undefined as
| TaskItemImpl
| undefined,
iconName: overrides.iconName,
iconImageUrl: overrides.iconImageUrl,
showClear: overrides.showClear,
Expand Down
23 changes: 14 additions & 9 deletions src/composables/queue/useJobMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -82,24 +83,28 @@ 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))
}

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)
}

Expand Down
20 changes: 16 additions & 4 deletions src/composables/queue/useResultGallery.ts
Original file line number Diff line number Diff line change
@@ -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<T extends PreviewItem = PreviewItem> {
previewOutput?: T
}

/**
* Manages result gallery state and activation for queue items.
*/
export function useResultGallery(getFilteredTasks: () => any[]) {
export function useResultGallery<T extends PreviewItem>(
getFilteredTasks: () => TaskWithPreview<T>[]
) {
const galleryActiveIndex = ref(-1)
const galleryItems = shallowRef<ResultItemImpl[]>([])
const galleryItems = shallowRef<T[]>([])

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] : []
})
Expand Down
2 changes: 1 addition & 1 deletion src/composables/useCivitaiModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface CivitaiModelVersionResponse {
model: CivitaiModel
modelId: number
files: CivitaiModelFile[]
[key: string]: any
[key: string]: unknown
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/composables/useContextMenuTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
24 changes: 19 additions & 5 deletions src/composables/useLoad3dViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'

Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions src/extensions/core/imageCompare.ts
Original file line number Diff line number Diff line change
@@ -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',

Expand All @@ -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 =
Expand Down
18 changes: 10 additions & 8 deletions src/extensions/core/load3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -496,22 +500,20 @@ 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')
console.error(msg)
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('\\', '/')

Expand Down
Loading