mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-25 17:24:07 +00:00
Merge Phase 2 integration (5 tasks for circular dependency reduction)
Amp-Thread-ID: https://ampcode.com/threads/T-019c0094-0176-767f-9b8f-8cae71452582 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
@@ -5,9 +5,10 @@
|
||||
severity="info"
|
||||
icon="pi pi-user"
|
||||
pt:text="w-full"
|
||||
data-testid="current-user-indicator"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div data-testid="current-user-indicator">
|
||||
<div class="tabular-nums">
|
||||
{{ $t('g.currentUser') }}: {{ userStore.currentUser?.username }}
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue'
|
||||
import { useQueueStore } from '@/stores/queueStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const AssetsSidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/AssetsSidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useAssetsSidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'assets',
|
||||
@@ -11,7 +14,7 @@ export const useAssetsSidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.assets',
|
||||
tooltip: 'sideToolbar.assets',
|
||||
label: 'sideToolbar.labels.assets',
|
||||
component: markRaw(AssetsSidebarTab),
|
||||
component: AssetsSidebarTab,
|
||||
type: 'vue',
|
||||
iconBadge: () => {
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import ModelLibrarySidebarTab from '@/components/sidebar/tabs/ModelLibrarySidebarTab.vue'
|
||||
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
|
||||
const ModelLibrarySidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/ModelLibrarySidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useModelLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'model-library',
|
||||
@@ -12,7 +15,7 @@ export const useModelLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.modelLibrary',
|
||||
tooltip: 'sideToolbar.modelLibrary',
|
||||
label: 'sideToolbar.labels.models',
|
||||
component: markRaw(ModelLibrarySidebarTab),
|
||||
component: ModelLibrarySidebarTab,
|
||||
type: 'vue',
|
||||
iconBadge: () => {
|
||||
if (isElectron()) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import NodeLibrarySidebarTab from '@/components/sidebar/tabs/NodeLibrarySidebarTab.vue'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const NodeLibrarySidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/NodeLibrarySidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useNodeLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'node-library',
|
||||
@@ -10,7 +13,7 @@ export const useNodeLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.nodeLibrary',
|
||||
tooltip: 'sideToolbar.nodeLibrary',
|
||||
label: 'sideToolbar.labels.nodes',
|
||||
component: markRaw(NodeLibrarySidebarTab),
|
||||
component: NodeLibrarySidebarTab,
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import { LGraphButton } from './LGraphButton'
|
||||
import type { LGraphButtonOptions } from './LGraphButton'
|
||||
import { LGraphCanvas } from './LGraphCanvas'
|
||||
import { LLink } from './LLink'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { Reroute } from './Reroute'
|
||||
import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots'
|
||||
import type { IDrawBoundingOptions } from './draw'
|
||||
import { NullGraphError } from './infrastructure/NullGraphError'
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
RenderShape,
|
||||
TitleMode
|
||||
} from './types/globalEnums'
|
||||
import type { NodeId, RerouteId } from './types/ids'
|
||||
import type { ISerialisedNode, SubgraphIO } from './types/serialisation'
|
||||
import type {
|
||||
IBaseWidget,
|
||||
@@ -88,9 +89,9 @@ import { BaseWidget } from './widgets/BaseWidget'
|
||||
import { toConcreteWidget } from './widgets/widgetMap'
|
||||
import type { WidgetTypeMap } from './widgets/widgetMap'
|
||||
|
||||
// #region Types
|
||||
export type { NodeId } from './types/ids'
|
||||
|
||||
export type NodeId = number | string
|
||||
// #region Types
|
||||
|
||||
export type NodeProperty = string | number | boolean | object
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import type { Reroute } from './Reroute'
|
||||
import type {
|
||||
CanvasColour,
|
||||
INodeInputSlot,
|
||||
@@ -19,11 +19,12 @@ import type {
|
||||
Point,
|
||||
ReadonlyLinkNetwork
|
||||
} from './interfaces'
|
||||
import type { LinkId, NodeId, RerouteId } from './types/ids'
|
||||
import type { Serialisable, SerialisableLLink } from './types/serialisation'
|
||||
|
||||
const layoutMutations = useLayoutMutations()
|
||||
export type { LinkId } from './types/ids'
|
||||
|
||||
export type LinkId = number
|
||||
const layoutMutations = useLayoutMutations()
|
||||
|
||||
export type SerialisedLLinkArray = [
|
||||
id: LinkId,
|
||||
|
||||
@@ -2,9 +2,8 @@ import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMuta
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import { LGraphBadge } from './LGraphBadge'
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import { LLink } from './LLink'
|
||||
import type { LinkId } from './LLink'
|
||||
import type {
|
||||
CanvasColour,
|
||||
INodeInputSlot,
|
||||
@@ -17,11 +16,12 @@ import type {
|
||||
ReadonlyLinkNetwork
|
||||
} from './interfaces'
|
||||
import { distance, isPointInRect } from './measure'
|
||||
import type { LinkId, NodeId, RerouteId } from './types/ids'
|
||||
import type { Serialisable, SerialisableReroute } from './types/serialisation'
|
||||
|
||||
const layoutMutations = useLayoutMutations()
|
||||
export type { RerouteId } from './types/ids'
|
||||
|
||||
export type RerouteId = number
|
||||
const layoutMutations = useLayoutMutations()
|
||||
|
||||
/** The input or output slot that an incomplete reroute link is connected to. */
|
||||
export interface FloatingRerouteSlot {
|
||||
|
||||
@@ -9,66 +9,78 @@ import type { Reroute } from './Reroute'
|
||||
import type { SubgraphInput } from './subgraph/SubgraphInput'
|
||||
import type { SubgraphInputNode } from './subgraph/SubgraphInputNode'
|
||||
import type { SubgraphOutputNode } from './subgraph/SubgraphOutputNode'
|
||||
import type { LinkDirection } from './types/globalEnums'
|
||||
import type { LinkDirection, RenderShape } from './types/globalEnums'
|
||||
import type {
|
||||
CanvasColour,
|
||||
LinkId,
|
||||
NodeId,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
RerouteId,
|
||||
Size
|
||||
} from './types/index'
|
||||
import type { IBaseWidget } from './types/widgets'
|
||||
|
||||
// Re-export pure types from the types directory for backwards compatibility
|
||||
export type {
|
||||
CanvasColour,
|
||||
CompassCorners,
|
||||
Direction,
|
||||
LinkId,
|
||||
NodeId,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
ReadOnlyTypedArray,
|
||||
Rect,
|
||||
RerouteId,
|
||||
Size
|
||||
} from '../types/geometry'
|
||||
} from './types/index'
|
||||
|
||||
export type { LinkId, NodeId, RerouteId } from '../types/ids'
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
export type {
|
||||
HasBoundingRect,
|
||||
INodeFlags,
|
||||
INodeSlotBase,
|
||||
ISlotType,
|
||||
IWidgetLocator
|
||||
} from '../types/slots'
|
||||
|
||||
export type {
|
||||
Dictionary,
|
||||
MethodNames,
|
||||
NullableProperties,
|
||||
OptionalProps,
|
||||
RequiredProps,
|
||||
SharedIntersection,
|
||||
WhenNullish
|
||||
} from '../types/utility'
|
||||
|
||||
// Import types we need locally
|
||||
import type { CanvasColour, Point, Size } from '../types/geometry'
|
||||
import type { LinkId, NodeId, RerouteId } from '../types/ids'
|
||||
import type {
|
||||
HasBoundingRect,
|
||||
INodeInputSlotBase,
|
||||
INodeOutputSlotBase,
|
||||
INodeSlotBase,
|
||||
ISlotType,
|
||||
IWidgetLocator
|
||||
} from '../types/slots'
|
||||
|
||||
export interface NewNodePosition {
|
||||
node: LGraphNode
|
||||
newPos: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
|
||||
export type NullableProperties<T> = {
|
||||
[P in keyof T]: T[P] | null
|
||||
}
|
||||
|
||||
export interface IBoundaryNodes {
|
||||
top: LGraphNode
|
||||
right: LGraphNode
|
||||
bottom: LGraphNode
|
||||
left: LGraphNode
|
||||
/**
|
||||
* If {@link T} is `null` or `undefined`, evaluates to {@link Result}. Otherwise, evaluates to {@link T}.
|
||||
* Useful for functions that return e.g. `undefined` when a param is nullish.
|
||||
*/
|
||||
export type WhenNullish<T, Result> =
|
||||
| (T & {})
|
||||
| (T extends null ? Result : T extends undefined ? Result : T & {})
|
||||
|
||||
/** A type with each of the {@link Properties} made optional. */
|
||||
export type OptionalProps<T, Properties extends keyof T> = Omit<
|
||||
T,
|
||||
Properties
|
||||
> & { [K in Properties]?: T[K] }
|
||||
|
||||
/** A type with each of the {@link Properties} marked as required. */
|
||||
export type RequiredProps<T, Properties extends keyof T> = Omit<
|
||||
T,
|
||||
Properties
|
||||
> & { [K in Properties]-?: T[K] }
|
||||
|
||||
/** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */
|
||||
export type SharedIntersection<T1, T2> = {
|
||||
[P in keyof T1 as P extends keyof T2 ? P : never]: T1[P]
|
||||
} & {
|
||||
[P in keyof T2 as P extends keyof T1 ? P : never]: T2[P]
|
||||
}
|
||||
|
||||
/**
|
||||
* Any object that has a {@link boundingRect}.
|
||||
*/
|
||||
export interface HasBoundingRect {
|
||||
/**
|
||||
* A rectangle that represents the outer edges of the item.
|
||||
*
|
||||
* Used for various calculations, such as overlap, selective rendering, and click checks.
|
||||
* For most items, this is cached position & size as `x, y, width, height`.
|
||||
* Some items (such as nodes and slots) may extend above and/or to the left of their {@link pos}.
|
||||
* @readonly
|
||||
* @see {@link move}
|
||||
*/
|
||||
readonly boundingRect: ReadOnlyRect
|
||||
}
|
||||
|
||||
/** An object containing a set of child objects */
|
||||
@@ -233,22 +245,109 @@ export interface IFoundSlot extends IInputOrOutput {
|
||||
link_pos: Point
|
||||
}
|
||||
|
||||
/** Union of property names that are of type Match */
|
||||
type KeysOfType<T, Match> = Exclude<
|
||||
{ [P in keyof T]: T[P] extends Match ? P : never }[keyof T],
|
||||
undefined
|
||||
>
|
||||
|
||||
/** The names of all (optional) methods and functions in T */
|
||||
export type MethodNames<T> = KeysOfType<
|
||||
T,
|
||||
((...args: unknown[]) => unknown) | undefined
|
||||
>
|
||||
export interface NewNodePosition {
|
||||
node: LGraphNode
|
||||
newPos: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
}
|
||||
export interface IBoundaryNodes {
|
||||
top: LGraphNode
|
||||
right: LGraphNode
|
||||
bottom: LGraphNode
|
||||
left: LGraphNode
|
||||
}
|
||||
|
||||
export type Direction = 'top' | 'bottom' | 'left' | 'right'
|
||||
|
||||
/** Resize handle positions (compass points) */
|
||||
export type CompassCorners = 'NE' | 'SE' | 'SW' | 'NW'
|
||||
|
||||
/**
|
||||
* Full slot interface with runtime-dependent properties.
|
||||
* Extends the base slot from types/slots.ts with LLink reference.
|
||||
* A string that represents a specific data / slot type, e.g. `STRING`.
|
||||
*
|
||||
* Can be comma-delimited to specify multiple allowed types, e.g. `STRING,INT`.
|
||||
*/
|
||||
export interface INodeSlot extends INodeSlotBase {
|
||||
export type ISlotType = number | string
|
||||
|
||||
export interface INodeSlot extends HasBoundingRect {
|
||||
/**
|
||||
* The name of the slot in English.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The localized name of the slot to display in the UI.
|
||||
* Takes higher priority than {@link name} if set.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
localized_name?: string
|
||||
/**
|
||||
* The name of the slot to display in the UI, modified by the user.
|
||||
* Takes higher priority than {@link display_name} if set.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
label?: string
|
||||
|
||||
type: ISlotType
|
||||
dir?: LinkDirection
|
||||
removable?: boolean
|
||||
shape?: RenderShape
|
||||
color_off?: CanvasColour
|
||||
color_on?: CanvasColour
|
||||
locked?: boolean
|
||||
nameLocked?: boolean
|
||||
pos?: Point
|
||||
/** @remarks Automatically calculated; not included in serialisation. */
|
||||
boundingRect: ReadOnlyRect
|
||||
/**
|
||||
* A list of floating link IDs that are connected to this slot.
|
||||
* This is calculated at runtime; it is **not** serialized.
|
||||
*/
|
||||
_floatingLinks?: Set<LLink>
|
||||
/**
|
||||
* Whether the slot has errors. It is **not** serialized.
|
||||
*/
|
||||
hasErrors?: boolean
|
||||
}
|
||||
|
||||
export interface INodeFlags {
|
||||
skip_repeated_outputs?: boolean
|
||||
allow_interaction?: boolean
|
||||
pinned?: boolean
|
||||
collapsed?: boolean
|
||||
/** Configuration setting for {@link LGraphNode.connectInputToOutput} */
|
||||
keepAllLinksOnBypass?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Full input slot interface with runtime-dependent properties.
|
||||
* A widget that is linked to a slot.
|
||||
*
|
||||
* This is set by the ComfyUI_frontend logic. See
|
||||
* https://github.com/Comfy-Org/ComfyUI_frontend/blob/b80e0e1a3c74040f328c4e344326c969c97f67e0/src/extensions/core/widgetInputs.ts#L659
|
||||
*/
|
||||
export interface INodeInputSlot extends INodeInputSlotBase, INodeSlot {
|
||||
export interface IWidgetLocator {
|
||||
name: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface INodeInputSlot extends INodeSlot {
|
||||
link: LinkId | null
|
||||
widget?: IWidgetLocator
|
||||
alwaysVisible?: boolean
|
||||
|
||||
/**
|
||||
* Internal use only; API is not finalised and may change at any time.
|
||||
*/
|
||||
@@ -259,10 +358,11 @@ export interface IWidgetInputSlot extends INodeInputSlot {
|
||||
widget: IWidgetLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* Full output slot interface with runtime-dependent properties.
|
||||
*/
|
||||
export interface INodeOutputSlot extends INodeOutputSlotBase, INodeSlot {}
|
||||
export interface INodeOutputSlot extends INodeSlot {
|
||||
links: LinkId[] | null
|
||||
_data?: unknown
|
||||
slot_index?: number
|
||||
}
|
||||
|
||||
/** Links */
|
||||
export interface ConnectingLink extends IInputOrOutput {
|
||||
|
||||
29
src/lib/litegraph/src/types/geometry.ts
Normal file
29
src/lib/litegraph/src/types/geometry.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number]
|
||||
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect =
|
||||
| [x: number, y: number, width: number, height: number]
|
||||
| Float64Array
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
export type ReadOnlyTypedArray<T extends Float64Array> = Omit<
|
||||
Readonly<T>,
|
||||
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
|
||||
>
|
||||
|
||||
/** A 2D vector as `[x, y]` */
|
||||
export type Vector2 = [x: number, y: number]
|
||||
|
||||
/** A 4D vector as `[x, y, z, w]` */
|
||||
export type Vector4 = [x: number, y: number, z: number, w: number]
|
||||
|
||||
/** Margin values as `[top, right, bottom, left]` */
|
||||
export type Margin = [top: number, right: number, bottom: number, left: number]
|
||||
8
src/lib/litegraph/src/types/ids.ts
Normal file
8
src/lib/litegraph/src/types/ids.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/** Unique identifier for a node in the graph */
|
||||
export type NodeId = number | string
|
||||
|
||||
/** Unique identifier for a link between nodes */
|
||||
export type LinkId = number
|
||||
|
||||
/** Unique identifier for a reroute point on a link */
|
||||
export type RerouteId = number
|
||||
14
src/lib/litegraph/src/types/index.ts
Normal file
14
src/lib/litegraph/src/types/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type {
|
||||
Margin,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
ReadOnlyTypedArray,
|
||||
Rect,
|
||||
Size,
|
||||
Vector2,
|
||||
Vector4
|
||||
} from './geometry'
|
||||
|
||||
export type { LinkId, NodeId, RerouteId } from './ids'
|
||||
|
||||
export type { CanvasColour, INodeSlotBase, ISlotType } from './slots'
|
||||
37
src/lib/litegraph/src/types/slots.ts
Normal file
37
src/lib/litegraph/src/types/slots.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { LinkDirection, RenderShape } from './globalEnums'
|
||||
import type { Point, ReadOnlyRect } from './geometry'
|
||||
|
||||
/** Union type for slot connection types - can be a string name or a numeric type code */
|
||||
export type ISlotType = string | number
|
||||
|
||||
/** Colour type for canvas elements */
|
||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||
|
||||
/**
|
||||
* Base interface for node slots (inputs and outputs).
|
||||
* Contains common properties shared between input and output slots.
|
||||
*/
|
||||
export interface INodeSlotBase {
|
||||
/** The unique name of the slot */
|
||||
name: string
|
||||
/** The type of the slot, used for connection compatibility */
|
||||
type: ISlotType
|
||||
/** Direction of the link connection */
|
||||
dir?: LinkDirection
|
||||
/** Whether the slot can be removed */
|
||||
removable?: boolean
|
||||
/** Visual shape of the slot */
|
||||
shape?: RenderShape
|
||||
/** Color when disconnected */
|
||||
color_off?: CanvasColour
|
||||
/** Color when connected */
|
||||
color_on?: CanvasColour
|
||||
/** Whether the slot is locked from modifications */
|
||||
locked?: boolean
|
||||
/** Whether the slot name is locked from changes */
|
||||
nameLocked?: boolean
|
||||
/** Position of the slot relative to the node */
|
||||
pos?: Point
|
||||
/** Bounding rectangle of the slot for hit detection */
|
||||
boundingRect: ReadOnlyRect
|
||||
}
|
||||
26
src/platform/telemetry/authTracking.ts
Normal file
26
src/platform/telemetry/authTracking.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { authEventHook, userResolvedHook } from '@/stores/authEventBus'
|
||||
|
||||
import type { TelemetryProvider } from './types'
|
||||
|
||||
export function initAuthTracking(
|
||||
getTelemetry: () => TelemetryProvider | null
|
||||
): void {
|
||||
authEventHook.on((event) => {
|
||||
const telemetry = getTelemetry()
|
||||
if (!telemetry) return
|
||||
|
||||
if (event.type === 'login' || event.type === 'register') {
|
||||
telemetry.trackAuth({
|
||||
method: event.method,
|
||||
is_new_user: event.is_new_user
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
userResolvedHook.on((event) => {
|
||||
const telemetry = getTelemetry()
|
||||
if (!telemetry) return
|
||||
|
||||
telemetry.identify?.(event.userId)
|
||||
})
|
||||
}
|
||||
@@ -16,12 +16,13 @@
|
||||
*/
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
import { initAuthTracking } from './authTracking'
|
||||
import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider'
|
||||
import type { TelemetryProvider } from './types'
|
||||
import { authEventHook } from './userIdentityBus'
|
||||
|
||||
// Singleton instance
|
||||
let _telemetryProvider: TelemetryProvider | null = null
|
||||
let _authTrackingInitialized = false
|
||||
|
||||
/**
|
||||
* Telemetry factory - conditionally creates provider based on distribution
|
||||
@@ -36,12 +37,10 @@ export function useTelemetry(): TelemetryProvider | null {
|
||||
if (isCloud) {
|
||||
_telemetryProvider = new MixpanelTelemetryProvider()
|
||||
|
||||
authEventHook.on(({ method, isNewUser }) => {
|
||||
_telemetryProvider?.trackAuth({
|
||||
method,
|
||||
is_new_user: isNewUser
|
||||
})
|
||||
})
|
||||
if (!_authTrackingInitialized) {
|
||||
initAuthTracking(() => _telemetryProvider)
|
||||
_authTrackingInitialized = true
|
||||
}
|
||||
}
|
||||
// For OSS builds, _telemetryProvider stays null
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import type {
|
||||
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
import type { RemoteConfig } from '@/platform/remoteConfig/types'
|
||||
import { TelemetryEvents } from '../../types'
|
||||
import { userIdentityHook } from '../../userIdentityBus'
|
||||
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'
|
||||
|
||||
const DEFAULT_DISABLED_EVENTS = [
|
||||
@@ -119,12 +118,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
persistence: 'cookie',
|
||||
loaded: () => {
|
||||
this.isInitialized = true
|
||||
this.flushEventQueue()
|
||||
userIdentityHook.on(({ userId }) => {
|
||||
if (this.mixpanel && userId) {
|
||||
this.mixpanel.identify(userId)
|
||||
}
|
||||
})
|
||||
this.flushEventQueue() // flush events that were queued while initializing
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -205,6 +199,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
)
|
||||
}
|
||||
|
||||
identify(userId: string): void {
|
||||
if (this.mixpanel) {
|
||||
this.mixpanel.identify(userId)
|
||||
}
|
||||
}
|
||||
|
||||
trackSignupOpened(): void {
|
||||
this.trackEvent(TelemetryEvents.USER_SIGN_UP_OPENED)
|
||||
}
|
||||
|
||||
@@ -272,6 +272,9 @@ export interface WorkflowCreatedMetadata {
|
||||
* Core telemetry provider interface
|
||||
*/
|
||||
export interface TelemetryProvider {
|
||||
// User identification (called by auth event hooks)
|
||||
identify?(userId: string): void
|
||||
|
||||
// Authentication flow events
|
||||
trackSignupOpened(): void
|
||||
trackAuth(metadata: AuthMetadata): void
|
||||
|
||||
@@ -11,11 +11,15 @@ vi.mock('@/platform/distribution/types', () => ({ isCloud: false }))
|
||||
vi.mock('@/platform/updates/common/releaseService')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} })),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}))
|
||||
vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
...(actual as object),
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} })),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}
|
||||
})
|
||||
|
||||
describe('useReleaseStore', () => {
|
||||
let store: ReturnType<typeof useReleaseStore>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import WorkflowsSidebarTab from '@/components/sidebar/tabs/WorkflowsSidebarTab.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const WorkflowsSidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/WorkflowsSidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useWorkflowsSidebarTab = (): SidebarTabExtension => {
|
||||
const settingStore = useSettingStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -23,7 +26,7 @@ export const useWorkflowsSidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.workflows',
|
||||
tooltip: 'sideToolbar.workflows',
|
||||
label: 'sideToolbar.labels.workflows',
|
||||
component: markRaw(WorkflowsSidebarTab),
|
||||
component: WorkflowsSidebarTab,
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
||||
import { isCloud, isNightly } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -13,6 +14,77 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
|
||||
type ExtensionModule = { default?: ComfyExtension; extension?: ComfyExtension }
|
||||
|
||||
const coreExtensionModules = import.meta.glob<ExtensionModule>(
|
||||
'../extensions/core/*.ts',
|
||||
{ eager: false }
|
||||
)
|
||||
|
||||
async function loadCoreExtensions(
|
||||
registerExtension: (ext: ComfyExtension) => void
|
||||
) {
|
||||
const loadPromises = Object.entries(coreExtensionModules)
|
||||
.filter(([path]) => !path.endsWith('/index.ts'))
|
||||
.map(async ([path, loader]) => {
|
||||
try {
|
||||
const mod = await loader()
|
||||
const extension = mod.default ?? mod.extension
|
||||
if (extension && typeof extension === 'object' && 'name' in extension) {
|
||||
registerExtension(extension)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to load extension from ${path}:`, e)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(loadPromises)
|
||||
|
||||
if (isCloud) {
|
||||
try {
|
||||
await import('../extensions/core/cloudRemoteConfig')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudRemoteConfig:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await import('../extensions/core/cloudBadges')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudBadges:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await import('../extensions/core/cloudSessionCookie')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudSessionCookie:', e)
|
||||
}
|
||||
|
||||
if (window.__CONFIG__?.subscription_required) {
|
||||
try {
|
||||
await import('../extensions/core/cloudSubscription')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudSubscription:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCloud || isNightly) {
|
||||
try {
|
||||
await import('../extensions/core/cloudFeedbackTopbarButton')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudFeedbackTopbarButton:', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (isNightly && !isCloud) {
|
||||
try {
|
||||
await import('../extensions/core/nightlyBadges')
|
||||
} catch (e) {
|
||||
console.error('Failed to load nightlyBadges:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const useExtensionService = () => {
|
||||
const extensionStore = useExtensionStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -33,9 +105,7 @@ export const useExtensionService = () => {
|
||||
|
||||
const extensions = await api.getExtensions()
|
||||
|
||||
// Need to load core extensions first as some custom extensions
|
||||
// may depend on them.
|
||||
await import('../extensions/core/index')
|
||||
await loadCoreExtensions(registerExtension)
|
||||
extensionStore.captureCoreExtensions()
|
||||
await Promise.all(
|
||||
extensions
|
||||
|
||||
16
src/stores/authEventBus.ts
Normal file
16
src/stores/authEventBus.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createEventHook } from '@vueuse/core'
|
||||
|
||||
import type { AuthMetadata } from '@/platform/telemetry/types'
|
||||
|
||||
export interface AuthEvent extends AuthMetadata {
|
||||
type: 'login' | 'register' | 'logout' | 'password_reset'
|
||||
}
|
||||
|
||||
export interface UserResolvedEvent {
|
||||
userId: string
|
||||
email?: string | null
|
||||
displayName?: string | null
|
||||
}
|
||||
|
||||
export const authEventHook = createEventHook<AuthEvent>()
|
||||
export const userResolvedHook = createEventHook<UserResolvedEvent>()
|
||||
@@ -25,8 +25,8 @@ import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import { t } from '@/i18n'
|
||||
import { WORKSPACE_STORAGE_KEYS } from '@/platform/auth/workspace/workspaceConstants'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { authEventHook } from '@/platform/telemetry/userIdentityBus'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { authEventHook, userResolvedHook } from '@/stores/authEventBus'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import type { AuthHeader } from '@/types/authTypes'
|
||||
import type { operations } from '@/types/comfyRegistryTypes'
|
||||
@@ -154,7 +154,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
return
|
||||
}
|
||||
|
||||
void useDialogService().showErrorDialog(error, {
|
||||
useDialogService().showErrorDialog(error, {
|
||||
title: t('errorDialog.defaultTitle'),
|
||||
reportType: 'authenticationError'
|
||||
})
|
||||
@@ -324,9 +324,14 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
|
||||
if (isCloud) {
|
||||
void authEventHook.trigger({
|
||||
event: 'login',
|
||||
type: 'login',
|
||||
method: 'email',
|
||||
isNewUser: false
|
||||
is_new_user: false
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
@@ -345,9 +350,14 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
|
||||
if (isCloud) {
|
||||
void authEventHook.trigger({
|
||||
event: 'register',
|
||||
type: 'register',
|
||||
method: 'email',
|
||||
isNewUser: true
|
||||
is_new_user: true
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
@@ -364,9 +374,14 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
const isNewUser = additionalUserInfo?.isNewUser ?? false
|
||||
void authEventHook.trigger({
|
||||
event: isNewUser ? 'register' : 'login',
|
||||
type: isNewUser ? 'register' : 'login',
|
||||
method: 'google',
|
||||
isNewUser
|
||||
is_new_user: isNewUser
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
@@ -383,9 +398,14 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
const isNewUser = additionalUserInfo?.isNewUser ?? false
|
||||
void authEventHook.trigger({
|
||||
event: isNewUser ? 'register' : 'login',
|
||||
type: isNewUser ? 'register' : 'login',
|
||||
method: 'github',
|
||||
isNewUser
|
||||
is_new_user: isNewUser
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
60
src/types/appInterface.ts
Normal 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>
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,9 +46,11 @@ vi.mock('@/stores/workspace/colorPaletteStore', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@vueuse/core', async () => {
|
||||
vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
const { ref } = await import('vue')
|
||||
return {
|
||||
...(actual as object),
|
||||
whenever: vi.fn(),
|
||||
useStorage: vi.fn((_key, defaultValue) => {
|
||||
return ref(defaultValue)
|
||||
|
||||
Reference in New Issue
Block a user