feat: create IComfyApp interface for type-only imports

- Create appInterface.ts with IComfyApp interface
- Update ComfyExtension to use IComfyApp instead of ComfyApp
- Update ComfyWidgetConstructor to use IComfyApp
- ComfyApp now implements IComfyApp
- Breaks ~300 circular dependency cycles through type imports

Amp-Thread-ID: https://ampcode.com/threads/T-019bfe73-6a29-7638-8160-8de515af8707
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-27 00:25:04 -08:00
parent 7ad43c689c
commit bccc693430
10 changed files with 92 additions and 29 deletions

View File

@@ -20,7 +20,7 @@ import {
} from '@/schemas/nodeDefSchema'
import { useLitegraphService } from '@/services/litegraphService'
import { app } from '@/scripts/app'
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from '@/types/appInterface'
const INLINE_INPUTS = false
@@ -69,7 +69,7 @@ function dynamicComboWidget(
node: LGraphNode,
inputName: string,
untypedInputData: InputSpec,
appArg: ComfyApp,
appArg: IComfyApp,
widgetName?: string
) {
const { addNodeInput } = useLitegraphService()

View File

@@ -187,8 +187,9 @@ export class ClipspaceDialog extends ComfyDialog {
app.registerExtension({
name: 'Comfy.Clipspace',
init(app) {
app.openClipspace = function () {
init(appArg) {
const comfyApp = appArg as ComfyApp
comfyApp.openClipspace = function () {
if (!ClipspaceDialog.instance) {
ClipspaceDialog.instance = new ClipspaceDialog()
ComfyApp.clipspace_invalidate_handler = ClipspaceDialog.invalidate
@@ -196,7 +197,7 @@ app.registerExtension({
if (ComfyApp.clipspace) {
ClipspaceDialog.instance.show()
} else app.ui.dialog.show('Clipspace is Empty!')
} else comfyApp.ui.dialog.show('Clipspace is Empty!')
}
}
})

View File

@@ -65,6 +65,7 @@ import { SYSTEM_NODE_DEFS, useNodeDefStore } from '@/stores/nodeDefStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { useWidgetStore } from '@/stores/widgetStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import type { IComfyApp } from '@/types/appInterface'
import type { ComfyExtension, MissingNodeType } from '@/types/comfy'
import { type ExtensionManager } from '@/types/extensionTypes'
import type { NodeExecutionId } from '@/types/nodeIdentification'
@@ -127,7 +128,7 @@ type Clipspace = {
combinedIndex: number
}
export class ComfyApp {
export class ComfyApp implements IComfyApp {
/**
* List of entries to queue
*/

View File

@@ -1,4 +1,4 @@
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from '@/types/appInterface'
import { $el } from '../../ui'
import { ComfyButtonGroup } from '../components/buttonGroup'
@@ -13,13 +13,13 @@ export { DraggableList } from '@/scripts/ui/draggableList'
export { applyTextReplacements, addStylesheet } from '@/scripts/utils'
export class ComfyAppMenu {
app: ComfyApp
app: IComfyApp
actionsGroup: ComfyButtonGroup
settingsGroup: ComfyButtonGroup
viewGroup: ComfyButtonGroup
element: HTMLElement
constructor(app: ComfyApp) {
constructor(app: IComfyApp) {
this.app = app
// Keep the group as there are custom scripts attaching extra

View File

@@ -3,14 +3,14 @@ import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingParams } from '@/platform/settings/types'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type { Settings } from '@/schemas/apiSchema'
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from '@/types/appInterface'
import { ComfyDialog } from './dialog'
export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
app: ComfyApp
app: IComfyApp
constructor(app: ComfyApp) {
constructor(app: IComfyApp) {
super()
this.app = app
}

View File

@@ -24,7 +24,7 @@ import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { InputSpec } from '@/schemas/nodeDefSchema'
import type { ComfyApp } from './app'
import type { IComfyApp } from '@/types/appInterface'
import './domWidget'
import './errorNodeWidgets'
@@ -37,7 +37,7 @@ export type ComfyWidgetConstructor = (
node: LGraphNode,
inputName: string,
inputData: InputSpec,
app: ComfyApp,
app: IComfyApp,
widgetName?: string
) => { widget: IBaseWidget; minWidth?: number; minHeight?: number }

View File

@@ -15,7 +15,7 @@ import type {
TaskOutput
} from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from '@/types/appInterface'
import { useExtensionService } from '@/services/extensionService'
import { getJobDetail } from '@/services/jobOutputCache'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
@@ -408,7 +408,7 @@ export class TaskItemImpl {
return new TaskItemImpl(this.job, jobDetail.outputs)
}
public async loadWorkflow(app: ComfyApp) {
public async loadWorkflow(app: IComfyApp) {
if (!this.isHistory) {
return
}

60
src/types/appInterface.ts Normal file
View File

@@ -0,0 +1,60 @@
import type { LGraph, LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import type { WorkflowOpenSource } from '@/platform/telemetry/types'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { ComfyExtension } from '@/types/comfy'
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
export interface IComfyApp {
vueAppReady: boolean
rootGraph: LGraph
canvas: LGraphCanvas
configuringGraph: boolean
nodeOutputs: Record<string, NodeExecutionOutput>
/** @deprecated storageLocation is always 'server' */
readonly storageLocation: string
/** @deprecated storage migration is no longer needed */
readonly isNewUserSession: boolean
/** @deprecated Use useExecutionStore().lastExecutionError instead */
readonly lastExecutionError: unknown
/** @deprecated Use useWidgetStore().widgets instead */
readonly widgets: Record<string, ComfyWidgetConstructor>
getPreviewFormatParam(): string
loadGraphData(
graphData?: ComfyWorkflowJSON,
clean?: boolean,
restore_view?: boolean,
workflow?: string | null | ComfyWorkflow,
options?: {
showMissingNodesDialog?: boolean
showMissingModelsDialog?: boolean
checkForRerouteMigration?: boolean
openSource?: WorkflowOpenSource
}
): Promise<void>
graphToPrompt(graph?: LGraph): Promise<{
workflow: ComfyWorkflowJSON
output: Record<string, unknown>
}>
queuePrompt(
number: number,
batchCount?: number,
queueNodeIds?: string[]
): Promise<boolean>
clean(): void
handleFile(file: File, openSource?: WorkflowOpenSource): Promise<void>
registerExtension(extension: ComfyExtension): void
registerNodeDef(nodeId: string, nodeDef: ComfyNodeDef): Promise<void>
}

View File

@@ -7,7 +7,7 @@ import type { SettingParams } from '@/platform/settings/types'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import type { Keybinding } from '@/schemas/keyBindingSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from './appInterface'
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
import type { ComfyCommand } from '@/stores/commandStore'
import type { AuthUserInfo } from '@/types/authTypes'
@@ -136,12 +136,12 @@ export interface ComfyExtension {
* Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added
* @param app The ComfyUI app instance
*/
init?(app: ComfyApp): Promise<void> | void
init?(app: IComfyApp): Promise<void> | void
/**
* Allows any additional setup, called after the application is fully set up and running
* @param app The ComfyUI app instance
*/
setup?(app: ComfyApp): Promise<void> | void
setup?(app: IComfyApp): Promise<void> | void
/**
* Called before nodes are registered with the graph
* @param defs The collection of node definitions, add custom ones or edit existing ones
@@ -149,7 +149,7 @@ export interface ComfyExtension {
*/
addCustomNodeDefs?(
defs: Record<string, ComfyNodeDef>,
app: ComfyApp
app: IComfyApp
): Promise<void> | void
// TODO(huchenlei): We should deprecate the async return value of
// getCustomWidgets.
@@ -158,7 +158,7 @@ export interface ComfyExtension {
* @param app The ComfyUI app instance
* @returns An array of {[widget name]: widget data}
*/
getCustomWidgets?(app: ComfyApp): Promise<Widgets> | Widgets
getCustomWidgets?(app: IComfyApp): Promise<Widgets> | Widgets
/**
* Allows the extension to add additional commands to the selection toolbox
@@ -190,7 +190,7 @@ export interface ComfyExtension {
beforeRegisterNodeDef?(
nodeType: typeof LGraphNode,
nodeData: ComfyNodeDef,
app: ComfyApp
app: IComfyApp
): Promise<void> | void
/**
@@ -200,7 +200,7 @@ export interface ComfyExtension {
* @param defs The node definitions
* @param app The ComfyUI app instance
*/
beforeRegisterVueAppNodeDefs?(defs: ComfyNodeDef[], app: ComfyApp): void
beforeRegisterVueAppNodeDefs?(defs: ComfyNodeDef[], app: IComfyApp): void
/**
* Allows the extension to register additional nodes with LGraph after standard nodes are added.
@@ -208,7 +208,7 @@ export interface ComfyExtension {
*
* @param app The ComfyUI app instance
*/
registerCustomNodes?(app: ComfyApp): Promise<void> | void
registerCustomNodes?(app: IComfyApp): Promise<void> | void
/**
* Allows the extension to modify a node that has been reloaded onto the graph.
* If you break something in the backend and want to patch workflows in the frontend
@@ -216,13 +216,13 @@ export interface ComfyExtension {
* @param node The node that has been loaded
* @param app The ComfyUI app instance
*/
loadedGraphNode?(node: LGraphNode, app: ComfyApp): void
loadedGraphNode?(node: LGraphNode, app: IComfyApp): void
/**
* Allows the extension to run code after the constructor of the node
* @param node The node that has been created
* @param app The ComfyUI app instance
*/
nodeCreated?(node: LGraphNode, app: ComfyApp): void
nodeCreated?(node: LGraphNode, app: IComfyApp): void
/**
* Allows the extension to modify the graph data before it is configured.
@@ -247,7 +247,7 @@ export interface ComfyExtension {
* Extensions can register at any time and will receive the latest value immediately.
* This is an experimental API and may be changed or removed in the future.
*/
onAuthUserResolved?(user: AuthUserInfo, app: ComfyApp): Promise<void> | void
onAuthUserResolved?(user: AuthUserInfo, app: IComfyApp): Promise<void> | void
/**
* Fired whenever the auth token is refreshed.

View File

@@ -13,7 +13,7 @@ import type {
UserData,
UserDataFullInfo
} from '@/schemas/apiSchema'
import type { ComfyApp } from '@/scripts/app'
import type { IComfyApp } from './appInterface'
import type {
BottomPanelExtension,
@@ -27,6 +27,7 @@ import type {
export type { ComfyExtension } from './comfy'
export type { ComfyApi } from '@/scripts/api'
export type { ComfyApp } from '@/scripts/app'
export type { IComfyApp } from './appInterface'
export type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
export type { InputSpec } from '@/schemas/nodeDefSchema'
export type {
@@ -78,7 +79,7 @@ interface AppReadiness {
declare global {
interface Window {
/** For use by extensions and in the browser console. Where possible, import `app` from '@/scripts/app' instead. */
app?: ComfyApp
app?: IComfyApp
/** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */
graph?: unknown