Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
subscription page
  • Loading branch information
jtydhr88 committed Oct 17, 2025
commit 6f2c404178e59022c8eceb2a35cf9d382897b045
Binary file added public/assets/images/cloud-subscription.webm
Copy link
Contributor

Choose a reason for hiding this comment

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

@christian-byrne do we want assets here or should this just get uploaded to GCS and we serve up a publicly signed link?

Binary file not shown.
41 changes: 38 additions & 3 deletions src/components/actionbar/ComfyActionbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
)
"
/>
<ComfyQueueButton />
<SubscribeToRun v-if="!activeSubscription" />
<ComfyQueueButton v-else />
</div>
</Panel>
</div>
Expand All @@ -49,14 +50,48 @@ import {
} from '@vueuse/core'
import { clamp } from 'es-toolkit/compat'
import Panel from 'primevue/panel'
import { computed, nextTick, onMounted, ref, watch } from 'vue'

import type { Component } from 'vue'
import {
computed,
defineAsyncComponent,
nextTick,
onMounted,
ref,
watch
} from 'vue'

import { isCloud } from '@/platform/distribution/types'
import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { cn } from '@/utils/tailwindUtil'

import ComfyQueueButton from './ComfyQueueButton.vue'

const activeSubscription = ref(false)
const SubscribeToRun: Component | null = isCloud
? defineAsyncComponent(
() =>
import(
'../../platform/cloud/subscription/components/SubscribeToRun.vue'
)
)
: null

if (isCloud) {
void import('@/platform/cloud/subscription/composables/useSubscription').then(
({ useSubscription }) => {
const { isActiveSubscription } = useSubscription()
watch(
isActiveSubscription,
(value) => {
activeSubscription.value = value
},
{ immediate: true }
)
}
)
}

const settingsStore = useSettingStore()

const position = computed(() => settingsStore.get('Comfy.UseNewMenu'))
Expand Down
29 changes: 24 additions & 5 deletions src/components/topbar/TopbarBadge.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
<template>
<div class="flex items-center gap-2 bg-comfy-menu-secondary px-3">
<div
class="flex items-center gap-2 bg-comfy-menu-secondary"
:class="[{ 'flex-row-reverse': reverseOrder }, noPadding ? '' : 'px-3']"
>
<div
v-if="badge.label"
class="rounded-full bg-white px-1.5 py-0.5 text-xxxs font-semibold text-black"
:class="labelClass"
>
{{ badge.label }}
</div>
<div class="font-inter text-sm font-extrabold text-slate-100">
<div
class="font-inter text-sm font-extrabold text-slate-100"
:class="textClass"
>
{{ badge.text }}
</div>
</div>
</template>
<script setup lang="ts">
import type { TopbarBadge } from '@/types/comfy'

defineProps<{
badge: TopbarBadge
}>()
withDefaults(
defineProps<{
badge: TopbarBadge
reverseOrder?: boolean
noPadding?: boolean
labelClass?: string
textClass?: string
}>(),
{
reverseOrder: false,
noPadding: false,
labelClass: '',
textClass: ''
}
)
</script>
19 changes: 19 additions & 0 deletions src/components/topbar/TopbarBadges.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
v-for="badge in topbarBadgeStore.badges"
:key="badge.text"
:badge
:reverse-order="reverseOrder"
:no-padding="noPadding"
:label-class="labelClass"
:text-class="textClass"
/>
</div>
</template>
Expand All @@ -13,5 +17,20 @@ import { useTopbarBadgeStore } from '@/stores/topbarBadgeStore'

import TopbarBadge from './TopbarBadge.vue'

withDefaults(
defineProps<{
reverseOrder?: boolean
noPadding?: boolean
labelClass?: string
textClass?: string
}>(),
{
reverseOrder: false,
noPadding: false,
labelClass: '',
textClass: ''
}
)

const topbarBadgeStore = useTopbarBadgeStore()
</script>
3 changes: 2 additions & 1 deletion src/composables/auth/useFirebaseAuthActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const useFirebaseAuthActions = () => {
signUpWithEmail,
updatePassword,
deleteAccount,
accessError
accessError,
reportError
}
}
27 changes: 27 additions & 0 deletions src/extensions/core/cloudSubscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { watch } from 'vue'

import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useExtensionService } from '@/services/extensionService'

useExtensionService().registerExtension({
name: 'Comfy.CloudSubscription',

setup: isCloud
? async () => {
const { isLoggedIn } = useCurrentUser()
const { requireActiveSubscription } = useSubscription()

const checkSubscriptionStatus = () => {
if (!isLoggedIn.value) return

void requireActiveSubscription()
}

watch(() => isLoggedIn.value, checkSubscriptionStatus, {
immediate: true
})
}
: undefined
})
1 change: 1 addition & 0 deletions src/extensions/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ import './widgetInputs'

if (isCloud) {
import('./cloudBadge')
import('./cloudSubscription')
}
30 changes: 30 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,8 @@
"failedToInitiateCreditPurchase": "Failed to initiate credit purchase: {error}",
"failedToAccessBillingPortal": "Failed to access billing portal: {error}",
"failedToPurchaseCredits": "Failed to purchase credits: {error}",
"failedToFetchSubscription": "Failed to fetch subscription status: {error}",
"failedToInitiateSubscription": "Failed to initiate subscription: {error}",
"unauthorizedDomain": "Your domain {domain} is not authorized to use this service. Please contact {email} to add your domain to the whitelist.",
"useApiKeyTip": "Tip: Can't access normal login? Use the Comfy API Key option.",
"nothingSelected": "Nothing selected",
Expand Down Expand Up @@ -1914,6 +1916,34 @@
"added": "Added",
"accountInitialized": "Account initialized"
},
"subscription": {
"title": "Subscription",
"comfyCloud": "Comfy Cloud",
"beta": "BETA",
"perMonth": "USD / month",
"renewsDate": "Renews {date}",
"manageSubscription": "Manage subscription",
"apiNodesBalance": "\"API Nodes\" Credit Balance",
"apiNodesDescription": "For running commercial/proprietary models",
"totalCredits": "Total credits",
"viewUsageHistory": "View usage history",
"addApiCredits": "Add API credits",
"yourPlanIncludes": "Your plan includes:",
"viewMoreDetails": "View more details",
"learnMore": "Learn more",
"messageSupport": "Message support",
"invoiceHistory": "Invoice history",
"benefits": {
"benefit1": "$10 in monthly credits for API models — top up when needed",
"benefit2": "Up to 30 min runtime per job"
},
"required": {
"title": "Subscribe to",
"waitingForSubscription": "Complete your subscription in the new tab. We'll automatically detect when you're done!",
"subscribe": "Subscribe"
},
"subscribeToRun": "Subscribe to Run"
},
"userSettings": {
"title": "User Settings",
"name": "Name",
Expand Down
23 changes: 23 additions & 0 deletions src/platform/cloud/subscription/components/SubscribeToRun.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<Button
v-tooltip.bottom="{
value: $t('subscription.subscribeToRun'),
showDelay: 600
}"
class="subscribe-to-run-button"
:label="$t('subscription.subscribeToRun')"
icon="pi pi-lock"
severity="primary"
size="small"
data-testid="subscribe-to-run-button"
@click="showSubscriptionDialog"
/>
</template>

<script setup lang="ts">
import Button from 'primevue/button'

import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'

const { showSubscriptionDialog } = useSubscription()
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div class="flex flex-col gap-3">
<div class="flex items-start gap-2">
<i class="pi pi-check mt-1 text-sm" />
<span class="text-sm">
{{ $t('subscription.benefits.benefit1') }}
</span>
</div>

<div class="flex items-start gap-2">
<i class="pi pi-check mt-1 text-sm" />
<span class="text-sm">
{{ $t('subscription.benefits.benefit2') }}
</span>
</div>

<Button
:label="$t('subscription.viewMoreDetails')"
text
icon="pi pi-external-link"
icon-pos="left"
size="small"
class="self-start !p-0 text-sm hover:!bg-transparent [&]:!text-[inherit]"
@click="handleViewMoreDetails"
/>
</div>
</template>

<script setup lang="ts">
import Button from 'primevue/button'

const handleViewMoreDetails = () => {
window.open('https://www.comfy.org/cloud', '_blank')
}
</script>
Loading
Loading