diff --git a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue index 762ea7f2d6..8907ecf444 100644 --- a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue +++ b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue @@ -164,15 +164,18 @@ const queuePrompt = async (e: Event) => { ? 'Comfy.QueuePromptFront' : 'Comfy.QueuePrompt' - useTelemetry()?.trackRunButton({ subscribe_to_run: false }) - if (batchCount.value > 1) { useTelemetry()?.trackUiButtonClicked({ button_id: 'queue_run_multiple_batches_submitted' }) } - await commandStore.execute(commandId) + await commandStore.execute(commandId, { + metadata: { + subscribe_to_run: false, + trigger_source: 'button' + } + }) } diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index c6f8182d8a..966fe40e45 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -22,6 +22,7 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu import { useSettingStore } from '@/platform/settings/settingStore' import { SUPPORT_URL } from '@/platform/support/config' import { useTelemetry } from '@/platform/telemetry' +import type { ExecutionTriggerSource } from '@/platform/telemetry/types' import { useToastStore } from '@/platform/updates/common/toastStore' import { useWorkflowService } from '@/platform/workflow/core/services/workflowService' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' @@ -466,7 +467,11 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Queue Prompt', versionAdded: '1.3.7', category: 'essentials' as const, - function: async () => { + function: async (metadata?: { + subscribe_to_run?: boolean + trigger_source?: ExecutionTriggerSource + }) => { + useTelemetry()?.trackRunButton(metadata) if (!isActiveSubscription.value) { showSubscriptionDialog() return @@ -485,7 +490,11 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Queue Prompt (Front)', versionAdded: '1.3.7', category: 'essentials' as const, - function: async () => { + function: async (metadata?: { + subscribe_to_run?: boolean + trigger_source?: ExecutionTriggerSource + }) => { + useTelemetry()?.trackRunButton(metadata) if (!isActiveSubscription.value) { showSubscriptionDialog() return @@ -503,7 +512,11 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'pi pi-play', label: 'Queue Selected Output Nodes', versionAdded: '1.19.6', - function: async () => { + function: async (metadata?: { + subscribe_to_run?: boolean + trigger_source?: ExecutionTriggerSource + }) => { + useTelemetry()?.trackRunButton(metadata) if (!isActiveSubscription.value) { showSubscriptionDialog() return @@ -526,6 +539,7 @@ export function useCoreCommands(): ComfyCommand[] { // Get execution IDs for all selected output nodes and their descendants const executionIds = getExecutionIdsForSelectedNodes(selectedOutputNodes) + if (executionIds.length === 0) { toastStore.add({ severity: 'error', @@ -535,6 +549,7 @@ export function useCoreCommands(): ComfyCommand[] { }) return } + useTelemetry()?.trackWorkflowExecution() await app.queuePrompt(0, batchCount, executionIds) } }, diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 42408fa0dd..5b4146c2d8 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -1694,21 +1694,22 @@ const ext: ComfyExtension = { label: 'Convert selected nodes to group node', icon: 'pi pi-sitemap', versionAdded: '1.3.17', - function: convertSelectedNodesToGroupNode + function: () => convertSelectedNodesToGroupNode() }, { id: 'Comfy.GroupNode.UngroupSelectedGroupNodes', label: 'Ungroup selected group nodes', icon: 'pi pi-sitemap', versionAdded: '1.3.17', - function: ungroupSelectedGroupNodes + function: () => ungroupSelectedGroupNodes() }, { id: 'Comfy.GroupNode.ManageGroupNodes', label: 'Manage group nodes', icon: 'pi pi-cog', versionAdded: '1.3.17', - function: manageGroupNodes + function: (...args: unknown[]) => + manageGroupNodes(args[0] as string | undefined) } ], keybindings: [ diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index 2168c90d02..419ab6329d 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -17,6 +17,7 @@ import type { AuthMetadata, CreditTopupMetadata, ExecutionContext, + ExecutionTriggerSource, ExecutionErrorMetadata, ExecutionSuccessMetadata, HelpCenterClosedMetadata, @@ -65,6 +66,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { private mixpanel: OverridedMixpanel | null = null private eventQueue: QueuedEvent[] = [] private isInitialized = false + private lastTriggerSource: ExecutionTriggerSource | undefined constructor() { const token = window.__CONFIG__?.mixpanel_token @@ -196,7 +198,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { clearTopupUtil() } - trackRunButton(options?: { subscribe_to_run?: boolean }): void { + trackRunButton(options?: { + subscribe_to_run?: boolean + trigger_source?: ExecutionTriggerSource + }): void { const executionContext = this.getExecutionContext() const runButtonProperties: RunButtonProperties = { @@ -207,20 +212,14 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { total_node_count: executionContext.total_node_count, subgraph_count: executionContext.subgraph_count, has_api_nodes: executionContext.has_api_nodes, - api_node_names: executionContext.api_node_names + api_node_names: executionContext.api_node_names, + trigger_source: options?.trigger_source } + this.lastTriggerSource = options?.trigger_source this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties) } - trackRunTriggeredViaKeybinding(): void { - this.trackEvent(TelemetryEvents.RUN_TRIGGERED_KEYBINDING) - } - - trackRunTriggeredViaMenu(): void { - this.trackEvent(TelemetryEvents.RUN_TRIGGERED_MENU) - } - trackSurvey( stage: 'opened' | 'submitted', responses?: SurveyResponses @@ -323,7 +322,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { trackWorkflowExecution(): void { const context = this.getExecutionContext() - this.trackEvent(TelemetryEvents.EXECUTION_START, context) + const eventContext: ExecutionContext = { + ...context, + trigger_source: this.lastTriggerSource ?? 'unknown' + } + this.trackEvent(TelemetryEvents.EXECUTION_START, eventContext) + this.lastTriggerSource = undefined } trackExecutionError(metadata: ExecutionErrorMetadata): void { diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index 27253bddd1..f16304e7c8 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -47,6 +47,7 @@ export interface RunButtonProperties { subgraph_count: number has_api_nodes: boolean api_node_names: string[] + trigger_source?: ExecutionTriggerSource } /** @@ -69,6 +70,7 @@ export interface ExecutionContext { total_node_count: number has_api_nodes: boolean api_node_names: string[] + trigger_source?: ExecutionTriggerSource } /** @@ -265,9 +267,10 @@ export interface TelemetryProvider { trackAddApiCreditButtonClicked(): void trackApiCreditTopupButtonPurchaseClicked(amount: number): void trackApiCreditTopupSucceeded(): void - trackRunButton(options?: { subscribe_to_run?: boolean }): void - trackRunTriggeredViaKeybinding(): void - trackRunTriggeredViaMenu(): void + trackRunButton(options?: { + subscribe_to_run?: boolean + trigger_source?: ExecutionTriggerSource + }): void // Credit top-up tracking (composition with internal utilities) startTopupTracking(): void @@ -336,8 +339,6 @@ export const TelemetryEvents = { // Subscription Flow RUN_BUTTON_CLICKED: 'app:run_button_click', - RUN_TRIGGERED_KEYBINDING: 'app:run_triggered_keybinding', - RUN_TRIGGERED_MENU: 'app:run_triggered_menu', SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'app:subscription_required_modal_opened', SUBSCRIBE_NOW_BUTTON_CLICKED: 'app:subscribe_now_button_clicked', MONTHLY_SUBSCRIPTION_SUCCEEDED: 'app:monthly_subscription_succeeded', @@ -399,6 +400,12 @@ export const TelemetryEvents = { export type TelemetryEventName = (typeof TelemetryEvents)[keyof typeof TelemetryEvents] +export type ExecutionTriggerSource = + | 'button' + | 'keybinding' + | 'legacy_ui' + | 'unknown' + /** * Union type for all possible telemetry event properties */ diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index 67d190f1ff..6a428c974e 100644 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -476,7 +476,8 @@ export class ComfyUI { textContent: 'Queue Prompt', onclick: () => { if (isCloud) { - useTelemetry()?.trackRunTriggeredViaMenu() + useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' }) + useTelemetry()?.trackWorkflowExecution() } app.queuePrompt(0, this.batchCount) } @@ -583,7 +584,8 @@ export class ComfyUI { textContent: 'Queue Front', onclick: () => { if (isCloud) { - useTelemetry()?.trackRunTriggeredViaMenu() + useTelemetry()?.trackRunButton({ trigger_source: 'legacy_ui' }) + useTelemetry()?.trackWorkflowExecution() } app.queuePrompt(-1, this.batchCount) } diff --git a/src/services/keybindingService.ts b/src/services/keybindingService.ts index fff58fa573..d2f45e0053 100644 --- a/src/services/keybindingService.ts +++ b/src/services/keybindingService.ts @@ -1,7 +1,5 @@ import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings' -import { isCloud } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' -import { useTelemetry } from '@/platform/telemetry' import { app } from '@/scripts/app' import { useCommandStore } from '@/stores/commandStore' import { useDialogStore } from '@/stores/dialogStore' @@ -66,15 +64,20 @@ export const useKeybindingService = () => { // Prevent default browser behavior first, then execute the command event.preventDefault() - if ( - isCloud && - (keybinding.commandId === 'Comfy.QueuePrompt' || - keybinding.commandId === 'Comfy.QueuePromptFront' || - keybinding.commandId === 'Comfy.QueueSelectedOutputNodes') - ) { - useTelemetry()?.trackRunTriggeredViaKeybinding() + const runCommandIds = new Set([ + 'Comfy.QueuePrompt', + 'Comfy.QueuePromptFront', + 'Comfy.QueueSelectedOutputNodes' + ]) + if (runCommandIds.has(keybinding.commandId)) { + await commandStore.execute(keybinding.commandId, { + metadata: { + trigger_source: 'keybinding' + } + }) + } else { + await commandStore.execute(keybinding.commandId) } - await commandStore.execute(keybinding.commandId) return } diff --git a/src/stores/commandStore.ts b/src/stores/commandStore.ts index d3da5d85f7..ade180d373 100644 --- a/src/stores/commandStore.ts +++ b/src/stores/commandStore.ts @@ -9,7 +9,7 @@ import type { KeybindingImpl } from './keybindingStore' export interface ComfyCommand { id: string - function: () => void | Promise + function: (metadata?: Record) => void | Promise label?: string | (() => string) icon?: string | (() => string) @@ -24,7 +24,7 @@ export interface ComfyCommand { export class ComfyCommandImpl implements ComfyCommand { id: string - function: () => void | Promise + function: (metadata?: Record) => void | Promise _label?: string | (() => string) _icon?: string | (() => string) _tooltip?: string | (() => string) @@ -96,11 +96,17 @@ export const useCommandStore = defineStore('command', () => { const { wrapWithErrorHandlingAsync } = useErrorHandling() const execute = async ( commandId: string, - errorHandler?: (error: any) => void + options?: { + errorHandler?: (error: unknown) => void + metadata?: Record + } ) => { const command = getCommand(commandId) if (command) { - await wrapWithErrorHandlingAsync(command.function, errorHandler)() + await wrapWithErrorHandlingAsync( + () => command.function(options?.metadata), + options?.errorHandler + )() } else { throw new Error(`Command ${commandId} not found`) } diff --git a/src/stores/menuItemStore.ts b/src/stores/menuItemStore.ts index 242e4641c5..0a24769a1d 100644 --- a/src/stores/menuItemStore.ts +++ b/src/stores/menuItemStore.ts @@ -3,8 +3,6 @@ import type { MenuItem } from 'primevue/menuitem' import { ref } from 'vue' import { CORE_MENU_COMMANDS } from '@/constants/coreMenuCommands' -import { isCloud } from '@/platform/distribution/types' -import { useTelemetry } from '@/platform/telemetry' import type { ComfyExtension } from '@/types/comfy' import { useCommandStore } from './commandStore' @@ -64,17 +62,7 @@ export const useMenuItemStore = defineStore('menuItem', () => { .map( (command) => ({ - command: () => { - if ( - isCloud && - (command.id === 'Comfy.QueuePrompt' || - command.id === 'Comfy.QueuePromptFront' || - command.id === 'Comfy.QueueSelectedOutputNodes') - ) { - useTelemetry()?.trackRunTriggeredViaMenu() - } - return commandStore.execute(command.id) - }, + command: () => commandStore.execute(command.id), label: command.menubarLabel, icon: command.icon, tooltip: command.tooltip, diff --git a/src/types/extensionTypes.ts b/src/types/extensionTypes.ts index bc2b920ba9..2c96faa057 100644 --- a/src/types/extensionTypes.ts +++ b/src/types/extensionTypes.ts @@ -114,5 +114,11 @@ export interface ExtensionManager { export interface CommandManager { commands: ComfyCommand[] - execute(command: string, errorHandler?: (error: any) => void): void + execute( + command: string, + options?: { + errorHandler?: (error: unknown) => void + metadata?: Record + } + ): void }