refactor: activation events + contributes

This commit is contained in:
Rizumu Ayaka
2025-11-13 19:21:43 +08:00
committed by Alexander Brown
parent bb2c99749a
commit f1856b7a17
12 changed files with 298 additions and 15 deletions

View File

@@ -1,5 +1,7 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Cloud.Badges',
activationEvents: ['*'],
comfyCloud: true,
})

View File

@@ -1,5 +1,7 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Cloud.FeedbackButton',
activationEvents: ['*'],
comfyCloud: true,
})

View File

@@ -1,5 +1,7 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Cloud.RemoteConfig',
activationEvents: ['*'],
comfyCloud: true,
})

View File

@@ -1,5 +1,7 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Cloud.SessionCookie',
activationEvents: ['*'],
comfyCloud: true,
})

View File

@@ -1,6 +1,8 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Cloud.Subscription',
activationEvents: ['*'],
comfyCloud: {
subscriptionRequired: true,
},

View File

@@ -0,0 +1,111 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.Load3D',
activationEvents: ['onWidgets:contributes', 'onCommands:contributes', 'onSettings:contributes'],
contributes: [
{
name: 'Comfy.Preview3D',
widgets: ['PREVIEW_3D'],
},
{
name: 'Comfy.Load3D',
widgets: ['LOAD_3D'],
settings: [
{
id: 'Comfy.Load3D.ShowGrid',
category: ['3D', 'Scene', 'Initial Grid Visibility'],
name: 'Initial Grid Visibility',
tooltip:
'Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
type: 'boolean',
defaultValue: true,
experimental: true
},
{
id: 'Comfy.Load3D.BackgroundColor',
category: ['3D', 'Scene', 'Initial Background Color'],
name: 'Initial Background Color',
tooltip:
'Controls the default background color of the 3D scene. This setting determines the background appearance when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
type: 'color',
defaultValue: '282828',
experimental: true
},
{
id: 'Comfy.Load3D.CameraType',
category: ['3D', 'Camera', 'Initial Camera Type'],
name: 'Initial Camera Type',
tooltip:
'Controls whether the camera is perspective or orthographic by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
type: 'combo',
options: ['perspective', 'orthographic'],
defaultValue: 'perspective',
experimental: true
},
{
id: 'Comfy.Load3D.LightIntensity',
category: ['3D', 'Light', 'Initial Light Intensity'],
name: 'Initial Light Intensity',
tooltip:
'Sets the default brightness level of lighting in the 3D scene. This value determines how intensely lights illuminate objects when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
type: 'number',
defaultValue: 3,
experimental: true
},
{
id: 'Comfy.Load3D.LightIntensityMaximum',
category: ['3D', 'Light', 'Light Intensity Maximum'],
name: 'Light Intensity Maximum',
tooltip:
'Sets the maximum allowable light intensity value for 3D scenes. This defines the upper brightness limit that can be set when adjusting lighting in any 3D widget.',
type: 'number',
defaultValue: 10,
experimental: true
},
{
id: 'Comfy.Load3D.LightIntensityMinimum',
category: ['3D', 'Light', 'Light Intensity Minimum'],
name: 'Light Intensity Minimum',
tooltip:
'Sets the minimum allowable light intensity value for 3D scenes. This defines the lower brightness limit that can be set when adjusting lighting in any 3D widget.',
type: 'number',
defaultValue: 1,
experimental: true
},
{
id: 'Comfy.Load3D.LightAdjustmentIncrement',
category: ['3D', 'Light', 'Light Adjustment Increment'],
name: 'Light Adjustment Increment',
tooltip:
'Controls the increment size when adjusting light intensity in 3D scenes. A smaller step value allows for finer control over lighting adjustments, while a larger value results in more noticeable changes per adjustment.',
type: 'slider',
attrs: {
min: 0.1,
max: 1,
step: 0.1
},
defaultValue: 0.5,
experimental: true
},
{
id: 'Comfy.Load3D.3DViewerEnable',
category: ['3D', '3DViewer', 'Enable'],
name: 'Enable 3D Viewer (Beta)',
tooltip:
'Enables the 3D Viewer (Beta) for selected nodes. This feature allows you to visualize and interact with 3D models directly within the full size 3d viewer.',
type: 'boolean',
defaultValue: false,
experimental: true
}
],
commands: [
{
id: 'Comfy.3DViewer.Open3DViewer',
icon: 'pi pi-pencil',
label: 'Open 3D Viewer (Beta) for Selected Node',
}
],
},
],
})

View File

@@ -0,0 +1,12 @@
import { defineComfyExtConfig } from '@/extensions/utils'
export default defineComfyExtConfig({
name: 'Comfy.SaveGLB',
activationEvents: ['onWidgets:contributes', 'onCommands:contributes', 'onSettings:contributes'],
contributes: [
{
name: 'Comfy.SaveGLB',
widgets: ['SAVE_GLB'],
},
],
})

View File

@@ -1,14 +1,15 @@
import { dispatchComfyExtensions } from '../dispatch'
import type { ComfyExtensionConfigs, ComfyExtensionEntrance } from '../types'
export async function importExtensions() {
export async function registerExtensions() {
console.log('importExtensions running...')
const extConfigs = import.meta.glob(`./extensions/*/comfy.ext.config.ts`, {
// Since each config is small, we only import the default export and use eager mode for better tree-shaking and performance.
import: 'default',
eager: true,
})
const extensionEntrance = import.meta.glob(`./extensions/*/index.ts`)
}) as ComfyExtensionConfigs
const extensionEntrance = import.meta.glob(`./extensions/*/index.ts`) as ComfyExtensionEntrance
dispatchComfyExtensions({ configs: extConfigs, entrance: extensionEntrance })
}

View File

@@ -4,7 +4,7 @@ import type {
ComfyExtensionEntrance,
ComfyExtensionLoadContext
} from './types'
import { formatExtensions, shouldLoadExtension } from './utils'
import { formatExtensions, normalizationActivationEvents } from './utils'
const extLoadContext: ComfyExtensionLoadContext = {
get isCloud() {
@@ -22,11 +22,61 @@ export async function dispatchComfyExtensions(options: {
const { configs, entrance } = options
const extensions = formatExtensions(entrance, configs)
for (const extension of Object.values(extensions)) {
if (shouldLoadExtension(extLoadContext, extension.config)) {
const module = await extension.entry()
console.log('✅ extension', extension.name, 'loaded', extension, module)
// if (shouldLoadExtension(extLoadContext, extension.config)) {
// const module = await extension.entry()
// console.log('✅ extension', extension.name, 'loaded', extension, module)
// } else {
// console.log('❌ extension', extension.name, 'disabled', extension.config)
// }
const activationEvents = normalizationActivationEvents(
extLoadContext,
extension.config
)
if (!activationEvents.length) {
console.log(
'❌ extension',
extension.name,
'has no activation events',
extension.config
)
} else {
console.log('❌ extension', extension.name, 'disabled', extension.config)
console.log(
'🧶 extension',
extension.name,
'has activation events:',
activationEvents
)
}
activationEvents.forEach((event) =>
onceExtImportEvent(event, async ({ event }) => {
console.log(
'✅ extension',
extension.name,
'loaded by',
event,
extension
)
await extension.entry()
})
)
}
}
type EventCallback = (ctx: { event: string }) => void | Promise<void>
const eventMap = new Map<string, Set<EventCallback>>()
export async function importExtensionsByEvent(event: string) {
const callbacks = eventMap.get(event)
if (!callbacks) return
eventMap.delete(event)
await Promise.all([...callbacks].map((cb) => cb({ event })))
}
function onceExtImportEvent(event: string, callback: EventCallback) {
if (eventMap.has(event)) {
eventMap.get(event)!.add(callback)
} else {
eventMap.set(event, new Set([callback]))
}
}

View File

@@ -1,4 +1,47 @@
import type { ComfyExtension } from '@/types'
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
type ExcludeFunctions<T> = T extends Function ? never : T
type StaticOnly<T> = {
// eslint-disable-next-line prettier/prettier
[K in keyof T as ExcludeFunctions<T[K]> extends never ? never : K]: ExcludeFunctions<T[K]>
}
type StaticComfyCommand = StaticOnly<
NonNullable<ComfyExtension['commands']>[number]
>
type StaticComfySettingParams = StaticOnly<
NonNullable<ComfyExtension['settings']>[number]
>
type StaticComfyKeybinding = StaticOnly<
NonNullable<ComfyExtension['keybindings']>[number]
>
type StaticComfyMenuCommandGroup = StaticOnly<
NonNullable<ComfyExtension['menuCommands']>[number]
>
export type ComfyExtensionActivationEvent =
| '*'
| 'onWidgets:contributes'
| 'onCommands:contributes'
| 'onSettings:contributes'
type ComfyExtensionContributes = {
name: string
widgets?: string[]
commands?: StaticComfyCommand[]
settings?: StaticComfySettingParams[]
keybindings?: StaticComfyKeybinding[]
}
export interface ComfyExtensionConfig {
name?: string
activationEvents: ComfyExtensionActivationEvent[]
contributes?: ComfyExtensionContributes | ComfyExtensionContributes[]
menuCommands?: StaticComfyMenuCommandGroup[]
comfyCloud?:
| boolean
| {

View File

@@ -39,13 +39,64 @@ export function formatExtensions(
return pkgs
}
export function shouldLoadExtension(
export function normalizationActivationEvents(
ctx: ComfyExtensionLoadContext,
extConfig: ComfyExtensionConfig | undefined
): boolean {
// No Config -> Load Extension
if (!extConfig) return true
config: ComfyExtensionConfig | undefined
): string[] {
if (!config) return ['*']
if (!checkAboutCloud(ctx, config)) return []
const { activationEvents, contributes: _contributes } = config
if (activationEvents.includes('*')) return ['*']
const contributes = _contributes
? Array.isArray(_contributes)
? _contributes
: [_contributes]
: []
const events: string[] = []
if (activationEvents.includes('onCommands:contributes')) {
for (const contribute of contributes) {
if (contribute.commands) {
for (const command of contribute.commands) {
events.push(`onCommands:${command}`)
}
}
}
}
if (activationEvents.includes('onSettings:contributes')) {
for (const contribute of contributes) {
if (contribute.settings) {
for (const setting of contribute.settings) {
events.push(`onSettings:${setting.id}`)
}
}
}
}
if (activationEvents.includes('onWidgets:contributes')) {
for (const contribute of contributes) {
events.push(`onWidgets:${contribute.name}`)
if (contribute.widgets) {
for (const widget of contribute.widgets) {
events.push(`onWidgets:${widget}`)
}
}
}
}
return events
}
function checkAboutCloud(
ctx: ComfyExtensionLoadContext,
extConfig: ComfyExtensionConfig
): boolean {
// Cloud Only Extension
const { comfyCloud } = extConfig
if (comfyCloud) {

View File

@@ -12,6 +12,7 @@ import { useWidgetStore } from '@/stores/widgetStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import type { ComfyExtension } from '@/types/comfy'
import type { AuthUserInfo } from '@/types/authTypes'
import { importExtensionsByEvent } from '@/extensions/dispatch'
export const useExtensionService = () => {
const extensionStore = useExtensionStore()
@@ -35,8 +36,12 @@ export const useExtensionService = () => {
// Need to load core extensions first as some custom extensions
// may depend on them.
const { importExtensions } = await import('../extensions/core/index')
await importExtensions()
const { registerExtensions } = await import('../extensions/core/index')
await registerExtensions()
// Import Immediately Loaded Extensions
await importExtensionsByEvent('*')
extensionStore.captureCoreExtensions()
await Promise.all(
extensions