Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<component
:is="currentButton"
:key="isActiveSubscription ? 'queue' : 'subscribe'"
:key="isSubscribedOrIsNotCloud ? 'queue' : 'subscribe'"
/>
</template>
<script setup lang="ts">
Expand All @@ -11,9 +11,9 @@ import ComfyQueueButton from '@/components/actionbar/ComfyRunButton/ComfyQueueBu
import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'

const { isActiveSubscription } = useSubscription()
const { isSubscribedOrIsNotCloud } = useSubscription()

const currentButton = computed(() =>
isActiveSubscription.value ? ComfyQueueButton : SubscribeToRunButton
isSubscribedOrIsNotCloud.value ? ComfyQueueButton : SubscribeToRunButton
)
</script>
9 changes: 7 additions & 2 deletions src/components/dialog/content/setting/CreditsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<UserCredit text-class="text-3xl font-bold" />
<Skeleton v-if="loading" width="2rem" height="2rem" />
<Button
v-else-if="isActiveSubscription"
v-else-if="isSubscribedOrIsNotCloud"
:label="$t('credits.purchaseCredits')"
:loading="loading"
@click="handlePurchaseCreditsClick"
Expand Down Expand Up @@ -125,6 +125,7 @@ import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.v
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useExternalLink } from '@/composables/useExternalLink'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import { useCommandStore } from '@/stores/commandStore'
Expand All @@ -144,7 +145,11 @@ const authStore = useFirebaseAuthStore()
const authActions = useFirebaseAuthActions()
const commandStore = useCommandStore()
const telemetry = useTelemetry()
const { isActiveSubscription } = useSubscription()
const subscription = isCloud ? useSubscription() : null
const isSubscribedOrIsNotCloud = computed(() => {
if (!isCloud) return true
return subscription?.isSubscribedOrIsNotCloud.value ?? false
})
const loading = computed(() => authStore.loading)
const balanceLoading = computed(() => authStore.isFetchingBalance)

Expand Down
2 changes: 1 addition & 1 deletion src/components/topbar/CurrentUserPopover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ vi.mock('@/stores/firebaseAuthStore', () => ({
const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: vi.fn(() => ({
isActiveSubscription: { value: true },
isSubscribedOrIsNotCloud: { value: true },
fetchStatus: mockFetchStatus
}))
}))
Expand Down
9 changes: 6 additions & 3 deletions src/components/topbar/CurrentUserPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
</div>
</div>

<div v-if="isActiveSubscription" class="flex items-center justify-between">
<div
v-if="isSubscribedOrIsNotCloud"
class="flex items-center justify-between"
>
<div class="flex flex-col gap-1">
<UserCredit text-class="text-2xl" />
<Button
Expand Down Expand Up @@ -68,7 +71,7 @@
/>

<Button
v-if="isActiveSubscription"
v-if="isSubscribedOrIsNotCloud"
class="justify-start"
:label="$t(planSettingsLabel)"
icon="pi pi-receipt"
Expand Down Expand Up @@ -122,7 +125,7 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
useCurrentUser()
const authActions = useFirebaseAuthActions()
const dialogService = useDialogService()
const { isActiveSubscription, fetchStatus } = useSubscription()
const { isSubscribedOrIsNotCloud, fetchStatus } = useSubscription()

const handleOpenUserSettings = () => {
dialogService.showSettingsDialog('user')
Expand Down
6 changes: 4 additions & 2 deletions src/composables/auth/useFirebaseAuthActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ export const useFirebaseAuthActions = () => {
)

const purchaseCredits = wrapWithErrorHandlingAsync(async (amount: number) => {
const { isActiveSubscription } = useSubscription()
if (!isActiveSubscription.value) return
if (isCloud) {
const { isSubscribedOrIsNotCloud } = useSubscription()
if (!isSubscribedOrIsNotCloud.value) return
}

const response = await authStore.initiateCreditPurchase({
amount_micros: usdToMicros(amount),
Expand Down
25 changes: 18 additions & 7 deletions src/composables/useCoreCommands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ComputedRef } from 'vue'

import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
Expand Down Expand Up @@ -47,6 +49,7 @@ import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { isCloud } from '@/platform/distribution/types'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
Expand All @@ -63,7 +66,10 @@ import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTyp

import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog'

const { isActiveSubscription, showSubscriptionDialog } = useSubscription()
const defaultSubscriptionState: Pick<ComputedRef<boolean>, 'value'> = {
value: true
}
const noop = () => {}
Comment on lines +69 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using computed() for better type safety.

The manual object with Pick<ComputedRef<boolean>, 'value'> type works correctly but is non-idiomatic. Using Vue's computed() would provide stronger type guarantees and consistency with the reactivity system.

Apply this diff for a cleaner implementation:

+import { computed } from 'vue'
+
-const defaultSubscriptionState: Pick<ComputedRef<boolean>, 'value'> = {
-  value: true
-}
+const defaultSubscriptionState = computed(() => true)
 const noop = () => {}

This ensures defaultSubscriptionState has the exact same type as subscription?.isSubscribedOrIsNotCloud from the cloud path.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const defaultSubscriptionState: Pick<ComputedRef<boolean>, 'value'> = {
value: true
}
const noop = () => {}
const defaultSubscriptionState = computed(() => true)
const noop = () => {}
🤖 Prompt for AI Agents
In src/composables/useCoreCommands.ts around lines 69-72, replace the manual
object typed as Pick<ComputedRef<boolean>,'value'> with a proper Vue computed
ref so defaultSubscriptionState is a ComputedRef<boolean> (e.g. const
defaultSubscriptionState = computed(() => true)); also ensure computed is
imported from 'vue' if it's not already; leave noop as-is. This makes
defaultSubscriptionState have the exact same reactive type as
subscription?.isSubscribedOrIsNotCloud.


const moveSelectedNodesVersionAdded = '1.22.2'

Expand All @@ -85,6 +91,11 @@ export function useCoreCommands(): ComfyCommand[] {
useSelectedLiteGraphItems()
const getTracker = () => workflowStore.activeWorkflow?.changeTracker

const subscription = isCloud ? useSubscription() : null
const subscriptionState =
subscription?.isSubscribedOrIsNotCloud ?? defaultSubscriptionState
const subscriptionDialog = subscription?.showSubscriptionDialog ?? noop

const moveSelectedNodes = (
positionUpdater: (pos: Point, gridSize: number) => Point
) => {
Expand Down Expand Up @@ -475,8 +486,8 @@ export function useCoreCommands(): ComfyCommand[] {
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
if (!subscriptionState.value) {
subscriptionDialog()
return
}

Expand All @@ -498,8 +509,8 @@ export function useCoreCommands(): ComfyCommand[] {
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
if (!subscriptionState.value) {
subscriptionDialog()
return
}

Expand All @@ -520,8 +531,8 @@ export function useCoreCommands(): ComfyCommand[] {
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
if (!subscriptionState.value) {
subscriptionDialog()
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const emit = defineEmits<{
subscribed: []
}>()

const { subscribe, isActiveSubscription, fetchStatus } = useSubscription()
const { subscribe, isSubscribedOrIsNotCloud, fetchStatus } = useSubscription()
const telemetry = useTelemetry()

const isLoading = ref(false)
Expand All @@ -76,7 +76,7 @@ const startPollingSubscriptionStatus = () => {

await fetchStatus()

if (isActiveSubscription.value) {
if (isSubscribedOrIsNotCloud.value) {
stopPolling()
telemetry?.trackMonthlySubscriptionSucceeded()
emit('subscribed')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="flex items-baseline gap-2">
<span class="text-2xl font-inter font-semibold leading-tight">
{{
isActiveSubscription
isSubscribedOrIsNotCloud
? $t('subscription.title')
: $t('subscription.titleUnsubscribed')
}}
Expand All @@ -27,7 +27,7 @@
}}</span>
</div>
<div
v-if="isActiveSubscription"
v-if="isSubscribedOrIsNotCloud"
class="text-sm text-text-secondary"
>
<template v-if="isCancelled">
Expand All @@ -47,7 +47,7 @@
</div>
</div>
<Button
v-if="isActiveSubscription"
v-if="isSubscribedOrIsNotCloud"
:label="$t('subscription.manageSubscription')"
severity="secondary"
class="text-xs bg-interface-menu-component-surface-selected"
Expand Down Expand Up @@ -196,7 +196,7 @@
{{ $t('subscription.viewUsageHistory') }}
</a>
<Button
v-if="isActiveSubscription"
v-if="isSubscribedOrIsNotCloud"
:label="$t('subscription.addCredits')"
severity="secondary"
class="p-2 min-h-8 bg-interface-menu-component-surface-selected"
Expand Down Expand Up @@ -320,7 +320,7 @@ import { cn } from '@/utils/tailwindUtil'
const { buildDocsUrl } = useExternalLink()

const {
isActiveSubscription,
isSubscribedOrIsNotCloud,
isCancelled,
formattedRenewalDate,
formattedEndDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function useSubscriptionInternal() {
const { startCancellationWatcher, stopCancellationWatcher } =
useSubscriptionCancellationWatcher({
fetchStatus,
isActiveSubscription: isSubscribedOrIsNotCloud,
isSubscribedOrIsNotCloud,
subscriptionStatus,
telemetry,
shouldWatchCancellation
Expand Down Expand Up @@ -223,7 +223,7 @@ function useSubscriptionInternal() {

return {
// State
isActiveSubscription: isSubscribedOrIsNotCloud,
isSubscribedOrIsNotCloud,
isCancelled,
formattedRenewalDate,
formattedEndDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const CANCELLATION_BACKOFF_MULTIPLIER = 3 // 5s, 15s, 45s, 135s intervals

type CancellationWatcherOptions = {
fetchStatus: () => Promise<CloudSubscriptionStatusResponse | null | void>
isActiveSubscription: ComputedRef<boolean>
isSubscribedOrIsNotCloud: ComputedRef<boolean>
subscriptionStatus: Ref<CloudSubscriptionStatusResponse | null>
telemetry: Pick<TelemetryProvider, 'trackMonthlySubscriptionCancelled'> | null
shouldWatchCancellation: () => boolean
}

export function useSubscriptionCancellationWatcher({
fetchStatus,
isActiveSubscription,
isSubscribedOrIsNotCloud,
subscriptionStatus,
telemetry,
shouldWatchCancellation
Expand Down Expand Up @@ -73,7 +73,7 @@ export function useSubscriptionCancellationWatcher({
try {
await fetchStatus()

if (!isActiveSubscription.value) {
if (!isSubscribedOrIsNotCloud.value) {
if (!cancellationTracked.value) {
cancellationTracked.value = true
try {
Expand Down
10 changes: 8 additions & 2 deletions src/scripts/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,11 +676,17 @@ export class ComfyApp {
'Payment Required: Please add credits to your account to use this node.'
)
) {
const { isActiveSubscription } = useSubscription()
if (isActiveSubscription.value) {
if (!isCloud) {
useDialogService().showTopUpCreditsDialog({
isInsufficientCredits: true
})
} else {
const { isSubscribedOrIsNotCloud } = useSubscription()
if (isSubscribedOrIsNotCloud.value) {
useDialogService().showTopUpCreditsDialog({
isInsufficientCredits: true
})
}
}
} else {
useDialogService().showExecutionErrorDialog(detail)
Expand Down
6 changes: 4 additions & 2 deletions src/services/dialogService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,10 @@ export const useDialogService = () => {
function showTopUpCreditsDialog(options?: {
isInsufficientCredits?: boolean
}) {
const { isActiveSubscription } = useSubscription()
if (!isActiveSubscription.value) return
if (isCloud) {
const { isSubscribedOrIsNotCloud } = useSubscription()
if (!isSubscribedOrIsNotCloud.value) return
}

return dialogStore.showDialog({
key: 'top-up-credits',
Expand Down
2 changes: 1 addition & 1 deletion tests-ui/tests/composables/useCoreCommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({

vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: vi.fn(() => ({
isActiveSubscription: vi.fn().mockReturnValue(true),
isSubscribedOrIsNotCloud: { value: true },
showSubscriptionDialog: vi.fn()
}))
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SubscriptionPanel from '@/platform/cloud/subscription/components/Subscrip

// Mock composables
const mockSubscriptionData = {
isActiveSubscription: false,
isSubscribedOrIsNotCloud: false,
isCancelled: false,
formattedRenewalDate: '2024-12-31',
formattedEndDate: '2024-12-31',
Expand Down Expand Up @@ -120,14 +120,14 @@ describe('SubscriptionPanel', () => {

describe('subscription state functionality', () => {
it('shows correct UI for active subscription', () => {
mockSubscriptionData.isActiveSubscription = true
mockSubscriptionData.isSubscribedOrIsNotCloud = true
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Manage Subscription')
expect(wrapper.text()).toContain('Add Credits')
})

it('shows correct UI for inactive subscription', () => {
mockSubscriptionData.isActiveSubscription = false
mockSubscriptionData.isSubscribedOrIsNotCloud = false
const wrapper = createWrapper()
expect(wrapper.findComponent({ name: 'SubscribeButton' }).exists()).toBe(
true
Expand All @@ -137,14 +137,14 @@ describe('SubscriptionPanel', () => {
})

it('shows renewal date for active non-cancelled subscription', () => {
mockSubscriptionData.isActiveSubscription = true
mockSubscriptionData.isSubscribedOrIsNotCloud = true
mockSubscriptionData.isCancelled = false
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Renews 2024-12-31')
})

it('shows expiry date for cancelled subscription', () => {
mockSubscriptionData.isActiveSubscription = true
mockSubscriptionData.isSubscribedOrIsNotCloud = true
mockSubscriptionData.isCancelled = true
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Expires 2024-12-31')
Expand Down
Loading