mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-15 01:48:06 +00:00
Compare commits
2 Commits
bl/fix-job
...
ever-prese
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06a4e63804 | ||
|
|
d73363fc29 |
@@ -1,19 +1,16 @@
|
||||
<template>
|
||||
<component
|
||||
:is="currentButton"
|
||||
:key="isActiveSubscription ? 'queue' : 'subscribe'"
|
||||
/>
|
||||
<div class="relative">
|
||||
<ComfyQueueButton />
|
||||
<SubscribeToRunButton
|
||||
v-if="!isActiveSubscription"
|
||||
class="absolute inset-0 z-10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ComfyQueueButton from '@/components/actionbar/ComfyRunButton/ComfyQueueButton.vue'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue'
|
||||
|
||||
const { isActiveSubscription } = useBillingContext()
|
||||
|
||||
const currentButton = computed(() =>
|
||||
isActiveSubscription.value ? ComfyQueueButton : SubscribeToRunButton
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -137,7 +137,7 @@ const authStore = useFirebaseAuthStore()
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const commandStore = useCommandStore()
|
||||
const telemetry = useTelemetry()
|
||||
const { isActiveSubscription } = useBillingContext()
|
||||
const { isActiveSubscription, subscription } = useBillingContext()
|
||||
const loading = computed(() => authStore.loading)
|
||||
const balanceLoading = computed(() => authStore.isFetchingBalance)
|
||||
|
||||
@@ -160,7 +160,9 @@ watch(
|
||||
|
||||
const handlePurchaseCreditsClick = () => {
|
||||
// Track purchase credits entry from Settings > Credits panel
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked()
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked({
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
>
|
||||
<WorkflowTabs />
|
||||
<TopbarBadges />
|
||||
<TopbarSubscribeButton />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -140,6 +141,7 @@ import NodePropertiesPanel from '@/components/rightSidePanel/RightSidePanel.vue'
|
||||
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
|
||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue'
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
|
||||
@@ -27,6 +27,20 @@
|
||||
>
|
||||
{{ subscriptionTierName }}
|
||||
</span>
|
||||
<Button
|
||||
v-if="isFreeTier"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
class="mt-2 whitespace-nowrap"
|
||||
:style="{
|
||||
background: 'var(--color-subscription-button-gradient)',
|
||||
color: 'var(--color-white)',
|
||||
borderColor: 'transparent'
|
||||
}"
|
||||
@click="handleSubscribeForMore"
|
||||
>
|
||||
{{ $t('subscription.subscribeForMore') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Credits Section -->
|
||||
@@ -170,6 +184,7 @@ const settingsDialog = useSettingsDialog()
|
||||
const dialogService = useDialogService()
|
||||
const {
|
||||
isActiveSubscription,
|
||||
isFreeTier,
|
||||
subscriptionTierName,
|
||||
subscriptionTier,
|
||||
fetchStatus
|
||||
@@ -208,7 +223,9 @@ const handleOpenUserSettings = () => {
|
||||
}
|
||||
|
||||
const handleOpenPlansAndPricing = () => {
|
||||
subscriptionDialog.showPricingTable()
|
||||
subscriptionDialog.showPricingTable({
|
||||
entry_point: 'popover_plans_and_pricing'
|
||||
})
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@@ -224,7 +241,9 @@ const handleOpenPlanAndCreditsSettings = () => {
|
||||
|
||||
const handleTopUp = () => {
|
||||
// Track purchase credits entry from avatar popover
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked()
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked({
|
||||
current_tier: subscriptionTier.value?.toLowerCase()
|
||||
})
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
emit('close')
|
||||
}
|
||||
@@ -242,6 +261,11 @@ const handleLogout = async () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleSubscribeForMore = () => {
|
||||
subscriptionDialog.show({ entry_point: 'popover_upgrade' })
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleSubscribed = async () => {
|
||||
await fetchStatus()
|
||||
}
|
||||
|
||||
35
src/components/topbar/TopbarSubscribeButton.vue
Normal file
35
src/components/topbar/TopbarSubscribeButton.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<Button
|
||||
v-if="isFreeTier"
|
||||
class="mr-2 shrink-0 whitespace-nowrap"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
:style="{
|
||||
background: 'var(--color-subscription-button-gradient)',
|
||||
color: 'var(--color-white)',
|
||||
borderColor: 'transparent'
|
||||
}"
|
||||
data-testid="topbar-subscribe-button"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ $t('subscription.subscribeForMore') }}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
|
||||
const { isFreeTier, showSubscriptionDialog } = useBillingContext()
|
||||
|
||||
function handleClick() {
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackSubscription('subscribe_clicked', {
|
||||
current_tier: 'free'
|
||||
})
|
||||
}
|
||||
showSubscriptionDialog()
|
||||
}
|
||||
</script>
|
||||
@@ -71,7 +71,8 @@ import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const moveSelectedNodesVersionAdded = '1.22.2'
|
||||
export function useCoreCommands(): ComfyCommand[] {
|
||||
const { isActiveSubscription, showSubscriptionDialog } = useBillingContext()
|
||||
const { isActiveSubscription, showSubscriptionDialog, subscription } =
|
||||
useBillingContext()
|
||||
const workflowService = useWorkflowService()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const settingsDialog = useSettingsDialog()
|
||||
@@ -495,7 +496,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
useTelemetry()?.trackRunButton({
|
||||
...metadata,
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
return
|
||||
@@ -518,7 +522,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
useTelemetry()?.trackRunButton({
|
||||
...metadata,
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
return
|
||||
@@ -540,7 +547,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
useTelemetry()?.trackRunButton({
|
||||
...metadata,
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
return
|
||||
|
||||
@@ -2274,6 +2274,7 @@
|
||||
},
|
||||
"subscribeToRun": "Subscribe",
|
||||
"subscribeToRunFull": "Subscribe to Run",
|
||||
"subscribeForMore": "Upgrade",
|
||||
"subscribeNow": "Subscribe Now",
|
||||
"subscribeToComfyCloud": "Subscribe to Comfy Cloud",
|
||||
"workspaceNotSubscribed": "This workspace is not on a subscription",
|
||||
|
||||
@@ -40,11 +40,14 @@ const buttonLabel = computed(() =>
|
||||
: t('subscription.subscribeToRun')
|
||||
)
|
||||
|
||||
const { showSubscriptionDialog } = useBillingContext()
|
||||
const { showSubscriptionDialog, subscription } = useBillingContext()
|
||||
|
||||
const handleSubscribeToRun = () => {
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackRunButton({ subscribe_to_run: true })
|
||||
useTelemetry()?.trackRunButton({
|
||||
subscribe_to_run: true,
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
}
|
||||
|
||||
showSubscriptionDialog()
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
v-if="isActiveSubscription"
|
||||
variant="primary"
|
||||
class="rounded-lg px-4 py-2 text-sm font-normal text-text-primary"
|
||||
@click="showSubscriptionDialog"
|
||||
@click="handleUpgradePlan"
|
||||
>
|
||||
{{ $t('subscription.upgradePlan') }}
|
||||
</Button>
|
||||
@@ -234,7 +234,11 @@ const {
|
||||
isYearlySubscription
|
||||
} = useSubscription()
|
||||
|
||||
const { show: showSubscriptionDialog } = useSubscriptionDialog()
|
||||
const { showPricingTable } = useSubscriptionDialog()
|
||||
|
||||
function handleUpgradePlan() {
|
||||
showPricingTable({ entry_point: 'settings_upgrade_plan' })
|
||||
}
|
||||
|
||||
const tierKey = computed(() => {
|
||||
const tier = subscriptionTier.value
|
||||
|
||||
@@ -153,7 +153,7 @@ const emit = defineEmits<{
|
||||
close: [subscribed: boolean]
|
||||
}>()
|
||||
|
||||
const { fetchStatus, isActiveSubscription } = useBillingContext()
|
||||
const { fetchStatus, isActiveSubscription, subscription } = useBillingContext()
|
||||
|
||||
const isSubscriptionEnabled = (): boolean =>
|
||||
Boolean(isCloud && window.__CONFIG__?.subscription_required)
|
||||
@@ -236,6 +236,10 @@ watch(
|
||||
() => isActiveSubscription.value,
|
||||
(isActive) => {
|
||||
if (isActive && showCustomPricingTable.value) {
|
||||
telemetry?.trackSubscriptionSucceeded({
|
||||
tier: subscription.value?.tier?.toLowerCase(),
|
||||
duration: subscription.value?.duration?.toLowerCase()
|
||||
})
|
||||
emit('close', true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +136,7 @@ function useSubscriptionInternal() {
|
||||
const showSubscriptionDialog = (options?: {
|
||||
reason?: SubscriptionDialogReason
|
||||
}) => {
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackSubscription('modal_opened', {
|
||||
current_tier: subscriptionTier.value?.toLowerCase(),
|
||||
reason: options?.reason
|
||||
})
|
||||
}
|
||||
|
||||
// modal_opened tracking is handled by useSubscriptionDialog.show()/showPricingTable()
|
||||
void showSubscriptionRequiredDialog(options)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useSubscriptionActions() {
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const commandStore = useCommandStore()
|
||||
const telemetry = useTelemetry()
|
||||
const { fetchStatus } = useBillingContext()
|
||||
const { fetchStatus, subscription } = useBillingContext()
|
||||
|
||||
const isLoadingSupport = ref(false)
|
||||
|
||||
@@ -24,6 +24,11 @@ export function useSubscriptionActions() {
|
||||
})
|
||||
|
||||
const handleAddApiCredits = () => {
|
||||
if (isCloud) {
|
||||
telemetry?.trackAddApiCreditButtonClicked({
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
}
|
||||
void dialogService.showTopUpCreditsDialog()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineAsyncComponent } from 'vue'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
|
||||
|
||||
@@ -13,19 +15,38 @@ export type SubscriptionDialogReason =
|
||||
| 'out_of_credits'
|
||||
| 'top_up_blocked'
|
||||
|
||||
interface SubscriptionDialogOptions {
|
||||
reason?: SubscriptionDialogReason
|
||||
entry_point?: string
|
||||
}
|
||||
|
||||
export const useSubscriptionDialog = () => {
|
||||
const { flags } = useFeatureFlags()
|
||||
const dialogService = useDialogService()
|
||||
const dialogStore = useDialogStore()
|
||||
const workspaceStore = useTeamWorkspaceStore()
|
||||
const { isFreeTier } = useSubscription()
|
||||
const { isFreeTier, subscriptionTier } = useSubscription()
|
||||
|
||||
function hide() {
|
||||
dialogStore.closeDialog({ key: DIALOG_KEY })
|
||||
dialogStore.closeDialog({ key: FREE_TIER_DIALOG_KEY })
|
||||
}
|
||||
|
||||
function showPricingTable(options?: { reason?: SubscriptionDialogReason }) {
|
||||
function trackModalOpened(
|
||||
modalName: 'free_tier_upsell' | 'pricing_table',
|
||||
options?: SubscriptionDialogOptions
|
||||
) {
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackSubscription('modal_opened', {
|
||||
current_tier: subscriptionTier.value?.toLowerCase(),
|
||||
reason: options?.reason,
|
||||
entry_point: options?.entry_point,
|
||||
modal_name: modalName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function openPricingDialog(options?: SubscriptionDialogOptions) {
|
||||
const useWorkspaceVariant =
|
||||
flags.teamWorkspacesEnabled && !workspaceStore.isInPersonalWorkspace
|
||||
|
||||
@@ -61,8 +82,17 @@ export const useSubscriptionDialog = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function show(options?: { reason?: SubscriptionDialogReason }) {
|
||||
if (isFreeTier.value && workspaceStore.isInPersonalWorkspace) {
|
||||
function showPricingTable(options?: SubscriptionDialogOptions) {
|
||||
trackModalOpened('pricing_table', options)
|
||||
openPricingDialog(options)
|
||||
}
|
||||
|
||||
function show(options?: SubscriptionDialogOptions) {
|
||||
const isPersonalContext =
|
||||
workspaceStore.isInPersonalWorkspace || !flags.teamWorkspacesEnabled
|
||||
if (isFreeTier.value && isPersonalContext) {
|
||||
trackModalOpened('free_tier_upsell', options)
|
||||
|
||||
const component = defineAsyncComponent(
|
||||
() =>
|
||||
import('@/platform/cloud/subscription/components/FreeTierDialogContent.vue')
|
||||
@@ -75,6 +105,13 @@ export const useSubscriptionDialog = () => {
|
||||
reason: options?.reason,
|
||||
onClose: hide,
|
||||
onUpgrade: () => {
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackSubscription('subscribe_clicked', {
|
||||
current_tier: subscriptionTier.value?.toLowerCase(),
|
||||
reason: options?.reason,
|
||||
entry_point: options?.entry_point
|
||||
})
|
||||
}
|
||||
hide()
|
||||
showPricingTable(options)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
PageVisibilityMetadata,
|
||||
SettingChangedMetadata,
|
||||
SubscriptionMetadata,
|
||||
SubscriptionSucceededMetadata,
|
||||
SurveyResponses,
|
||||
TabCountMetadata,
|
||||
TelemetryDispatcher,
|
||||
@@ -77,16 +78,18 @@ export class TelemetryRegistry implements TelemetryDispatcher {
|
||||
this.dispatch((provider) => provider.trackBeginCheckout?.(metadata))
|
||||
}
|
||||
|
||||
trackMonthlySubscriptionSucceeded(): void {
|
||||
this.dispatch((provider) => provider.trackMonthlySubscriptionSucceeded?.())
|
||||
trackSubscriptionSucceeded(metadata?: SubscriptionSucceededMetadata): void {
|
||||
this.dispatch((provider) => provider.trackSubscriptionSucceeded?.(metadata))
|
||||
}
|
||||
|
||||
trackMonthlySubscriptionCancelled(): void {
|
||||
this.dispatch((provider) => provider.trackMonthlySubscriptionCancelled?.())
|
||||
}
|
||||
|
||||
trackAddApiCreditButtonClicked(): void {
|
||||
this.dispatch((provider) => provider.trackAddApiCreditButtonClicked?.())
|
||||
trackAddApiCreditButtonClicked(metadata?: { current_tier?: string }): void {
|
||||
this.dispatch((provider) =>
|
||||
provider.trackAddApiCreditButtonClicked?.(metadata)
|
||||
)
|
||||
}
|
||||
|
||||
trackApiCreditTopupButtonPurchaseClicked(amount: number): void {
|
||||
@@ -102,6 +105,7 @@ export class TelemetryRegistry implements TelemetryDispatcher {
|
||||
trackRunButton(options?: {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
current_tier?: string
|
||||
}): void {
|
||||
this.dispatch((provider) => provider.trackRunButton?.(options))
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
RunButtonProperties,
|
||||
SettingChangedMetadata,
|
||||
SubscriptionMetadata,
|
||||
SubscriptionSucceededMetadata,
|
||||
SurveyResponses,
|
||||
TabCountMetadata,
|
||||
TelemetryEventName,
|
||||
@@ -235,12 +236,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
this.trackEvent(eventName, metadata)
|
||||
}
|
||||
|
||||
trackAddApiCreditButtonClicked(): void {
|
||||
this.trackEvent(TelemetryEvents.ADD_API_CREDIT_BUTTON_CLICKED)
|
||||
trackAddApiCreditButtonClicked(metadata?: { current_tier?: string }): void {
|
||||
this.trackEvent(TelemetryEvents.ADD_API_CREDIT_BUTTON_CLICKED, metadata)
|
||||
}
|
||||
|
||||
trackMonthlySubscriptionSucceeded(): void {
|
||||
this.trackEvent(TelemetryEvents.MONTHLY_SUBSCRIPTION_SUCCEEDED)
|
||||
trackSubscriptionSucceeded(metadata?: SubscriptionSucceededMetadata): void {
|
||||
this.trackEvent(TelemetryEvents.SUBSCRIPTION_SUCCEEDED, metadata)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,6 +282,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
trackRunButton(options?: {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
current_tier?: string
|
||||
}): void {
|
||||
const executionContext = this.getExecutionContext()
|
||||
|
||||
@@ -295,7 +297,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
api_node_names: executionContext.api_node_names,
|
||||
has_toolkit_nodes: executionContext.has_toolkit_nodes,
|
||||
toolkit_node_names: executionContext.toolkit_node_names,
|
||||
trigger_source: options?.trigger_source
|
||||
trigger_source: options?.trigger_source,
|
||||
current_tier: options?.current_tier
|
||||
}
|
||||
|
||||
this.lastTriggerSource = options?.trigger_source
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface RunButtonProperties {
|
||||
has_toolkit_nodes: boolean
|
||||
toolkit_node_names: string[]
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
current_tier?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,6 +306,13 @@ export interface CheckoutAttributionMetadata {
|
||||
export interface SubscriptionMetadata {
|
||||
current_tier?: string
|
||||
reason?: SubscriptionDialogReason
|
||||
entry_point?: string
|
||||
modal_name?: 'free_tier_upsell' | 'pricing_table'
|
||||
}
|
||||
|
||||
export interface SubscriptionSucceededMetadata {
|
||||
tier?: string
|
||||
duration?: string
|
||||
}
|
||||
|
||||
export interface BeginCheckoutMetadata
|
||||
@@ -332,14 +340,15 @@ export interface TelemetryProvider {
|
||||
metadata?: SubscriptionMetadata
|
||||
): void
|
||||
trackBeginCheckout?(metadata: BeginCheckoutMetadata): void
|
||||
trackMonthlySubscriptionSucceeded?(): void
|
||||
trackSubscriptionSucceeded?(metadata?: SubscriptionSucceededMetadata): void
|
||||
trackMonthlySubscriptionCancelled?(): void
|
||||
trackAddApiCreditButtonClicked?(): void
|
||||
trackAddApiCreditButtonClicked?(metadata?: { current_tier?: string }): void
|
||||
trackApiCreditTopupButtonPurchaseClicked?(amount: number): void
|
||||
trackApiCreditTopupSucceeded?(): void
|
||||
trackRunButton?(options?: {
|
||||
subscribe_to_run?: boolean
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
current_tier?: string
|
||||
}): void
|
||||
|
||||
// Credit top-up tracking (composition with internal utilities)
|
||||
@@ -423,7 +432,7 @@ export const TelemetryEvents = {
|
||||
RUN_BUTTON_CLICKED: 'app:run_button_click',
|
||||
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',
|
||||
SUBSCRIPTION_SUCCEEDED: 'app:subscription_succeeded',
|
||||
MONTHLY_SUBSCRIPTION_CANCELLED: 'app:monthly_subscription_cancelled',
|
||||
ADD_API_CREDIT_BUTTON_CLICKED: 'app:add_api_credit_button_clicked',
|
||||
API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED:
|
||||
@@ -522,3 +531,4 @@ export type TelemetryEventProperties =
|
||||
| WorkflowCreatedMetadata
|
||||
| EnterLinearMetadata
|
||||
| SubscriptionMetadata
|
||||
| SubscriptionSucceededMetadata
|
||||
|
||||
@@ -296,7 +296,9 @@ const handleOpenWorkspaceSettings = () => {
|
||||
}
|
||||
|
||||
const handleOpenPlansAndPricing = () => {
|
||||
subscriptionDialog.showPricingTable()
|
||||
subscriptionDialog.showPricingTable({
|
||||
entry_point: 'popover_plans_and_pricing'
|
||||
})
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@@ -312,7 +314,9 @@ const handleOpenPlanAndCreditsSettings = () => {
|
||||
|
||||
const handleTopUp = () => {
|
||||
// Track purchase credits entry from avatar popover
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked()
|
||||
useTelemetry()?.trackAddApiCreditButtonClicked({
|
||||
current_tier: subscription.value?.tier?.toLowerCase()
|
||||
})
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@@ -462,7 +462,12 @@ function handleSubscribeWorkspace() {
|
||||
}
|
||||
|
||||
function handleUpgrade() {
|
||||
isFreeTierPlan.value ? showPricingTable() : showSubscriptionDialog()
|
||||
const options = { entry_point: 'settings_upgrade_plan' }
|
||||
if (isFreeTierPlan.value) {
|
||||
showPricingTable(options)
|
||||
} else {
|
||||
showSubscriptionDialog()
|
||||
}
|
||||
}
|
||||
const subscriptionTier = computed(() => subscription.value?.tier ?? null)
|
||||
const isYearlySubscription = computed(
|
||||
|
||||
@@ -78,9 +78,13 @@ import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { TIER_TO_KEY } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { getCheckoutAttribution } from '@/platform/telemetry/utils/checkoutAttribution'
|
||||
import type { PreviewSubscribeResponse } from '@/platform/workspace/api/workspaceApi'
|
||||
import { workspaceApi } from '@/platform/workspace/api/workspaceApi'
|
||||
import { useBillingOperationStore } from '@/platform/workspace/stores/billingOperationStore'
|
||||
@@ -104,9 +108,17 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const { subscribe, previewSubscribe, plans, fetchStatus, fetchBalance } =
|
||||
useBillingContext()
|
||||
const {
|
||||
subscribe,
|
||||
previewSubscribe,
|
||||
plans,
|
||||
fetchStatus,
|
||||
fetchBalance,
|
||||
subscription
|
||||
} = useBillingContext()
|
||||
|
||||
const telemetry = useTelemetry()
|
||||
const { resolvedUserInfo } = useCurrentUser()
|
||||
const billingOperationStore = useBillingOperationStore()
|
||||
const isPolling = computed(() => billingOperationStore.hasPendingOperations)
|
||||
|
||||
@@ -184,6 +196,22 @@ async function handleSubscribeClick(payload: {
|
||||
}
|
||||
}
|
||||
|
||||
async function trackCheckout(checkoutType: 'new' | 'change') {
|
||||
if (!resolvedUserInfo.value?.id || !selectedTierKey.value) return
|
||||
|
||||
const attribution = await getCheckoutAttribution()
|
||||
telemetry?.trackBeginCheckout({
|
||||
user_id: resolvedUserInfo.value.id,
|
||||
tier: selectedTierKey.value,
|
||||
cycle: selectedBillingCycle.value,
|
||||
checkout_type: checkoutType,
|
||||
...(subscription.value?.tier
|
||||
? { previous_tier: TIER_TO_KEY[subscription.value.tier] }
|
||||
: {}),
|
||||
...attribution
|
||||
})
|
||||
}
|
||||
|
||||
function handleBackToPricing() {
|
||||
checkoutStep.value = 'pricing'
|
||||
previewData.value = null
|
||||
@@ -194,6 +222,8 @@ async function handleAddCreditCard() {
|
||||
|
||||
isSubscribing.value = true
|
||||
try {
|
||||
await trackCheckout('new')
|
||||
|
||||
const planSlug = getApiPlanSlug(
|
||||
selectedTierKey.value,
|
||||
selectedBillingCycle.value
|
||||
@@ -208,6 +238,10 @@ async function handleAddCreditCard() {
|
||||
if (!response) return
|
||||
|
||||
if (response.status === 'subscribed') {
|
||||
telemetry?.trackSubscriptionSucceeded({
|
||||
tier: selectedTierKey.value ?? undefined,
|
||||
duration: selectedBillingCycle.value === 'yearly' ? 'annual' : 'monthly'
|
||||
})
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('subscription.required.pollingSuccess'),
|
||||
@@ -249,6 +283,8 @@ async function handleConfirmTransition() {
|
||||
|
||||
isSubscribing.value = true
|
||||
try {
|
||||
await trackCheckout('change')
|
||||
|
||||
const planSlug = getApiPlanSlug(
|
||||
selectedTierKey.value,
|
||||
selectedBillingCycle.value
|
||||
@@ -263,6 +299,10 @@ async function handleConfirmTransition() {
|
||||
if (!response) return
|
||||
|
||||
if (response.status === 'subscribed') {
|
||||
telemetry?.trackSubscriptionSucceeded({
|
||||
tier: selectedTierKey.value ?? undefined,
|
||||
duration: selectedBillingCycle.value === 'yearly' ? 'annual' : 'monthly'
|
||||
})
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('subscription.required.pollingSuccess'),
|
||||
|
||||
@@ -259,6 +259,7 @@ async function handleBuy() {
|
||||
const response = await workspaceApi.createTopup(amountCents)
|
||||
|
||||
if (response.status === 'completed') {
|
||||
telemetry?.trackApiCreditTopupSucceeded()
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('credits.topUp.purchaseSuccess'),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { t } from '@/i18n'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { workspaceApi } from '@/platform/workspace/api/workspaceApi'
|
||||
import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog'
|
||||
@@ -139,6 +140,15 @@ export const useBillingOperationStore = defineStore('billingOperation', () => {
|
||||
billingContext.fetchBalance()
|
||||
])
|
||||
|
||||
if (operation.type === 'subscription') {
|
||||
useTelemetry()?.trackSubscriptionSucceeded({
|
||||
tier: billingContext.subscription.value?.tier?.toLowerCase(),
|
||||
duration: billingContext.subscription.value?.duration?.toLowerCase()
|
||||
})
|
||||
} else if (operation.type === 'topup') {
|
||||
useTelemetry()?.trackApiCreditTopupSucceeded()
|
||||
}
|
||||
|
||||
// Close any open billing dialogs and show settings
|
||||
const dialogStore = useDialogStore()
|
||||
dialogStore.closeDialog({ key: 'subscription-required' })
|
||||
|
||||
@@ -185,26 +185,31 @@ defineExpose({ runButtonClick })
|
||||
root-class="text-base-foreground grid-cols-[auto_96px]"
|
||||
class="*:[.min-w-0]:w-24"
|
||||
/>
|
||||
<SubscribeToRunButton v-if="!isActiveSubscription" class="w-full mt-4" />
|
||||
<div v-else class="flex mt-4 gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
class="grow-1"
|
||||
size="lg"
|
||||
@click="runButtonClick"
|
||||
>
|
||||
<i class="icon-[lucide--play]" />
|
||||
{{ t('menu.run') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!executionStore.isIdle"
|
||||
variant="destructive"
|
||||
size="lg"
|
||||
class="w-10 p-2"
|
||||
@click="commandStore.execute('Comfy.Interrupt')"
|
||||
>
|
||||
<i class="icon-[lucide--x]" />
|
||||
</Button>
|
||||
<div class="relative mt-4">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
class="grow-1"
|
||||
size="lg"
|
||||
@click="runButtonClick"
|
||||
>
|
||||
<i class="icon-[lucide--play]" />
|
||||
{{ t('menu.run') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!executionStore.isIdle"
|
||||
variant="destructive"
|
||||
size="lg"
|
||||
class="w-10 p-2"
|
||||
@click="commandStore.execute('Comfy.Interrupt')"
|
||||
>
|
||||
<i class="icon-[lucide--x]" />
|
||||
</Button>
|
||||
</div>
|
||||
<SubscribeToRunButton
|
||||
v-if="!isActiveSubscription"
|
||||
class="absolute inset-0 w-full z-10"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
|
||||
@@ -11,6 +11,7 @@ import AppModeToolbar from '@/components/appMode/AppModeToolbar.vue'
|
||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||
import ModeToggle from '@/components/sidebar/ModeToggle.vue'
|
||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue'
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import TypeformPopoverButton from '@/components/ui/TypeformPopoverButton.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
@@ -82,6 +83,7 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
|
||||
<div class="flex h-full items-center">
|
||||
<WorkflowTabs />
|
||||
<TopbarBadges />
|
||||
<TopbarSubscribeButton />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user