refactor: start on removing FF for subscription tiers (#7596)

## Summary

Refactor: remove FF for subscription tier, remove legacy code for non
subscription tier logic.
 
## Review Focus

Preexisting cloud functionality impact.

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7596-refactor-start-on-removing-FF-for-subscription-tiers-2cc6d73d3650816bac3aef893e4f37cd)
by [Unito](https://www.unito.io)
This commit is contained in:
Simula_r
2025-12-19 17:52:37 -08:00
committed by GitHub
parent c0c284977d
commit ccb73186fb
13 changed files with 26 additions and 308 deletions

View File

@@ -14,13 +14,7 @@
class="p-1 text-amber-400" class="p-1 text-amber-400"
> >
<template #icon> <template #icon>
<i <i class="icon-[lucide--component]" />
:class="
flags.subscriptionTiersEnabled
? 'icon-[lucide--component]'
: 'pi pi-dollar'
"
/>
</template> </template>
</Tag> </Tag>
<div :class="textClass"> <div :class="textClass">
@@ -36,7 +30,6 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { formatCreditsFromCents } from '@/base/credits/comfyCredits' import { formatCreditsFromCents } from '@/base/credits/comfyCredits'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
const { textClass, showCreditsOnly } = defineProps<{ const { textClass, showCreditsOnly } = defineProps<{
@@ -45,7 +38,6 @@ const { textClass, showCreditsOnly } = defineProps<{
}>() }>()
const authStore = useFirebaseAuthStore() const authStore = useFirebaseAuthStore()
const { flags } = useFeatureFlags()
const balanceLoading = computed(() => authStore.isFetchingBalance) const balanceLoading = computed(() => authStore.isFetchingBalance)
const { t, locale } = useI18n() const { t, locale } = useI18n()

View File

@@ -1,6 +1,5 @@
<template> <template>
<!-- New Credits Design (default) --> <div class="flex w-112 flex-col gap-8 p-8">
<div v-if="useNewDesign" class="flex w-112 flex-col gap-8 p-8">
<!-- Header --> <!-- Header -->
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<h1 class="text-2xl font-semibold text-base-foreground m-0"> <h1 class="text-2xl font-semibold text-base-foreground m-0">
@@ -66,91 +65,32 @@
@click="handleBuy" @click="handleBuy"
/> />
</div> </div>
<!-- Legacy Design -->
<div v-else class="flex w-96 flex-col gap-10 p-2">
<div v-if="isInsufficientCredits" class="flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
{{ $t('credits.topUp.insufficientTitle') }}
</h1>
<p class="my-0 text-base">
{{ $t('credits.topUp.insufficientMessage') }}
</p>
</div>
<!-- Balance Section -->
<div class="flex items-center justify-between">
<div class="flex w-full flex-col gap-2">
<div class="text-base text-muted">
{{ $t('credits.yourCreditBalance') }}
</div>
<div class="flex w-full items-center justify-between">
<UserCredit text-class="text-2xl" />
<Button
outlined
severity="secondary"
:label="$t('credits.topUp.seeDetails')"
icon="pi pi-arrow-up-right"
@click="handleSeeDetails"
/>
</div>
</div>
</div>
<!-- Amount Input Section -->
<div class="flex flex-col gap-2">
<span class="text-sm text-muted"
>{{ $t('credits.topUp.quickPurchase') }}:</span
>
<div class="grid grid-cols-[2fr_1fr] gap-2">
<LegacyCreditTopUpOption
v-for="amount in amountOptions"
:key="amount"
:amount="amount"
:preselected="amount === preselectedAmountOption"
/>
<LegacyCreditTopUpOption :amount="100" :preselected="false" editable />
</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Button from 'primevue/button' import Button from 'primevue/button'
import { useToast } from 'primevue/usetoast' import { useToast } from 'primevue/usetoast'
import { computed, ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { creditsToUsd } from '@/base/credits/comfyCredits' import { creditsToUsd } from '@/base/credits/comfyCredits'
import UserCredit from '@/components/common/UserCredit.vue' import UserCredit from '@/components/common/UserCredit.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useTelemetry } from '@/platform/telemetry' import { useTelemetry } from '@/platform/telemetry'
import CreditTopUpOption from './credit/CreditTopUpOption.vue' import CreditTopUpOption from './credit/CreditTopUpOption.vue'
import LegacyCreditTopUpOption from './credit/LegacyCreditTopUpOption.vue'
interface CreditOption { interface CreditOption {
credits: number credits: number
description: string description: string
} }
const { const { isInsufficientCredits = false } = defineProps<{
isInsufficientCredits = false,
amountOptions = [5, 10, 20, 50],
preselectedAmountOption = 10
} = defineProps<{
isInsufficientCredits?: boolean isInsufficientCredits?: boolean
amountOptions?: number[]
preselectedAmountOption?: number
}>() }>()
const { flags } = useFeatureFlags()
const { formattedRenewalDate } = useSubscription() const { formattedRenewalDate } = useSubscription()
// Use feature flag to determine design - defaults to true (new design)
const useNewDesign = computed(() => flags.subscriptionTiersEnabled)
const { t } = useI18n() const { t } = useI18n()
const authActions = useFirebaseAuthActions() const authActions = useFirebaseAuthActions()
@@ -202,8 +142,4 @@ const handleBuy = async () => {
loading.value = false loading.value = false
} }
} }
const handleSeeDetails = async () => {
await authActions.accessBillingPortal()
}
</script> </script>

View File

@@ -1,119 +0,0 @@
<template>
<div class="flex items-center gap-2">
<Tag
severity="secondary"
icon="pi pi-wallet"
rounded
class="p-1 text-amber-400"
/>
<div v-if="editable" class="flex items-center gap-2">
<InputNumber
v-model="customAmount"
:min="1"
:max="1000"
:step="1"
show-buttons
:allow-empty="false"
:highlight-on-focus="true"
prefix="$"
pt:pc-input-text:root="w-28"
@blur="
(e: InputNumberBlurEvent) =>
(customAmount = clampUsd(Number(e.value)))
"
@input="
(e: InputNumberInputEvent) =>
(customAmount = clampUsd(Number(e.value)))
"
/>
<span class="text-xs text-muted">{{ formattedCredits }}</span>
</div>
<div v-else class="flex flex-col leading-tight">
<span class="text-xl font-semibold">{{ formattedCredits }}</span>
<span class="text-xs text-muted">{{ formattedUsd }}</span>
</div>
</div>
<ProgressSpinner v-if="loading" class="h-8 w-8" />
<Button
v-else
:severity="preselected ? 'primary' : 'secondary'"
:outlined="!preselected"
:label="$t('credits.topUp.buyNow')"
@click="handleBuyNow"
/>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import InputNumber from 'primevue/inputnumber'
import type {
InputNumberBlurEvent,
InputNumberInputEvent
} from 'primevue/inputnumber'
import ProgressSpinner from 'primevue/progressspinner'
import Tag from 'primevue/tag'
import { computed, onBeforeUnmount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import {
clampUsd,
formatCreditsFromUsd,
formatUsd
} from '@/base/credits/comfyCredits'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useTelemetry } from '@/platform/telemetry'
const authActions = useFirebaseAuthActions()
const telemetry = useTelemetry()
const {
amount,
preselected,
editable = false
} = defineProps<{
amount: number
preselected: boolean
editable?: boolean
}>()
const customAmount = ref(amount)
const didClickBuyNow = ref(false)
const loading = ref(false)
const { t, locale } = useI18n()
const displayUsdAmount = computed(() =>
editable ? clampUsd(Number(customAmount.value)) : clampUsd(amount)
)
const formattedCredits = computed(
() =>
`${formatCreditsFromUsd({
usd: displayUsdAmount.value,
locale: locale.value
})} ${t('credits.credits')}`
)
const formattedUsd = computed(
() => `$${formatUsd({ value: displayUsdAmount.value, locale: locale.value })}`
)
const handleBuyNow = async () => {
const creditAmount = displayUsdAmount.value
telemetry?.trackApiCreditTopupButtonPurchaseClicked(creditAmount)
loading.value = true
try {
await authActions.purchaseCredits(creditAmount)
didClickBuyNow.value = true
} finally {
loading.value = false
}
}
onBeforeUnmount(() => {
if (didClickBuyNow.value) {
// If clicked buy now, then returned back to the dialog and closed, fetch the balance
void authActions.fetchBalance()
}
})
</script>

View File

@@ -138,15 +138,6 @@ vi.mock('@/composables/useExternalLink', () => ({
})) }))
})) }))
// Mock useFeatureFlags
vi.mock('@/composables/useFeatureFlags', () => ({
useFeatureFlags: vi.fn(() => ({
flags: {
subscriptionTiersEnabled: true
}
}))
}))
// Mock useTelemetry // Mock useTelemetry
vi.mock('@/platform/telemetry', () => ({ vi.mock('@/platform/telemetry', () => ({
useTelemetry: vi.fn(() => ({ useTelemetry: vi.fn(() => ({

View File

@@ -42,7 +42,6 @@
formattedBalance formattedBalance
}}</span> }}</span>
<i <i
v-if="flags.subscriptionTiersEnabled"
v-tooltip="{ value: $t('credits.unified.tooltip'), showDelay: 300 }" v-tooltip="{ value: $t('credits.unified.tooltip'), showDelay: 300 }"
class="icon-[lucide--circle-help] cursor-help text-base text-muted-foreground mr-auto" class="icon-[lucide--circle-help] cursor-help text-base text-muted-foreground mr-auto"
/> />
@@ -147,7 +146,6 @@ import UserAvatar from '@/components/common/UserAvatar.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useExternalLink } from '@/composables/useExternalLink' import { useExternalLink } from '@/composables/useExternalLink'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue' import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
@@ -174,7 +172,6 @@ const {
fetchStatus fetchStatus
} = useSubscription() } = useSubscription()
const subscriptionDialog = useSubscriptionDialog() const subscriptionDialog = useSubscriptionDialog()
const { flags } = useFeatureFlags()
const { locale } = useI18n() const { locale } = useI18n()
const formattedBalance = computed(() => { const formattedBalance = computed(() => {

View File

@@ -1,7 +1,6 @@
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraphBadge } from '@/lib/litegraph/src/litegraph' import { LGraphBadge } from '@/lib/litegraph/src/litegraph'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { adjustColor } from '@/utils/colorUtil' import { adjustColor } from '@/utils/colorUtil'
@@ -10,7 +9,6 @@ componentIconSvg.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='oklch(83.01%25 0.163 83.16)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0zm-13.239 0a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0zm6.619 6.619a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0zm0-13.238a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z'/%3E%3C/svg%3E" "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='oklch(83.01%25 0.163 83.16)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0zm-13.239 0a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0zm6.619 6.619a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0zm0-13.238a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z'/%3E%3C/svg%3E"
export const usePriceBadge = () => { export const usePriceBadge = () => {
const { flags } = useFeatureFlags()
function updateSubgraphCredits(node: LGraphNode) { function updateSubgraphCredits(node: LGraphNode) {
if (!node.isSubgraphNode()) return if (!node.isSubgraphNode()) return
node.badges = node.badges.filter((b) => !isCreditsBadge(b)) node.badges = node.badges.filter((b) => !isCreditsBadge(b))
@@ -40,53 +38,26 @@ export const usePriceBadge = () => {
function isCreditsBadge(badge: LGraphBadge | (() => LGraphBadge)): boolean { function isCreditsBadge(badge: LGraphBadge | (() => LGraphBadge)): boolean {
const badgeInstance = typeof badge === 'function' ? badge() : badge const badgeInstance = typeof badge === 'function' ? badge() : badge
if (flags.subscriptionTiersEnabled) { return badgeInstance.icon?.image === componentIconSvg
return badgeInstance.icon?.image === componentIconSvg
} else {
return badgeInstance.icon?.unicode === '\ue96b'
}
} }
const colorPaletteStore = useColorPaletteStore() const colorPaletteStore = useColorPaletteStore()
function getCreditsBadge(price: string): LGraphBadge { function getCreditsBadge(price: string): LGraphBadge {
const isLightTheme = colorPaletteStore.completedActivePalette.light_theme const isLightTheme = colorPaletteStore.completedActivePalette.light_theme
if (flags.subscriptionTiersEnabled) { return new LGraphBadge({
return new LGraphBadge({ text: price,
text: price, iconOptions: {
iconOptions: { image: componentIconSvg,
image: componentIconSvg, size: 8
size: 8 },
}, fgColor:
fgColor: colorPaletteStore.completedActivePalette.colors.litegraph_base
colorPaletteStore.completedActivePalette.colors.litegraph_base .BADGE_FG_COLOR,
.BADGE_FG_COLOR, bgColor: isLightTheme
bgColor: isLightTheme ? adjustColor('#8D6932', { lightness: 0.5 })
? adjustColor('#8D6932', { lightness: 0.5 }) : '#8D6932'
: '#8D6932' })
})
} else {
return new LGraphBadge({
text: price,
iconOptions: {
unicode: '\ue96b',
fontFamily: 'PrimeIcons',
color: isLightTheme
? adjustColor('#FABC25', { lightness: 0.5 })
: '#FABC25',
bgColor: isLightTheme
? adjustColor('#654020', { lightness: 0.5 })
: '#654020',
fontSize: 8
},
fgColor:
colorPaletteStore.completedActivePalette.colors.litegraph_base
.BADGE_FG_COLOR,
bgColor: isLightTheme
? adjustColor('#8D6932', { lightness: 0.5 })
: '#8D6932'
})
}
} }
return { return {
getCreditsBadge, getCreditsBadge,

View File

@@ -13,7 +13,6 @@ export enum ServerFeatureFlag {
MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled', MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',
ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled', ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled',
PRIVATE_MODELS_ENABLED = 'private_models_enabled', PRIVATE_MODELS_ENABLED = 'private_models_enabled',
SUBSCRIPTION_TIERS_ENABLED = 'subscription_tiers_enabled',
ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled' ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled'
} }
@@ -58,16 +57,6 @@ export function useFeatureFlags() {
api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false) api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)
) )
}, },
get subscriptionTiersEnabled() {
// Check remote config first (from /api/features), fall back to websocket feature flags
return (
remoteConfig.value.subscription_tiers_enabled ??
api.getServerFeature(
ServerFeatureFlag.SUBSCRIPTION_TIERS_ENABLED,
true // Default to true (new design)
)
)
},
get onboardingSurveyEnabled() { get onboardingSurveyEnabled() {
return ( return (
remoteConfig.value.onboarding_survey_enabled ?? remoteConfig.value.onboarding_survey_enabled ??

View File

@@ -26,7 +26,6 @@
import Button from 'primevue/button' import Button from 'primevue/button'
import { computed, onBeforeUnmount, ref, watch } from 'vue' import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types' import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry' import { useTelemetry } from '@/platform/telemetry'
@@ -54,10 +53,7 @@ const emit = defineEmits<{
const { subscribe, isActiveSubscription, fetchStatus, showSubscriptionDialog } = const { subscribe, isActiveSubscription, fetchStatus, showSubscriptionDialog } =
useSubscription() useSubscription()
const { flags } = useFeatureFlags()
const shouldUseStripePricing = computed(
() => isCloud && Boolean(flags.subscriptionTiersEnabled)
)
const telemetry = useTelemetry() const telemetry = useTelemetry()
const isLoading = ref(false) const isLoading = ref(false)
@@ -112,7 +108,7 @@ const stopPolling = () => {
watch( watch(
[isAwaitingStripeSubscription, isActiveSubscription], [isAwaitingStripeSubscription, isActiveSubscription],
([awaiting, isActive]) => { ([awaiting, isActive]) => {
if (shouldUseStripePricing.value && awaiting && isActive) { if (isCloud && awaiting && isActive) {
emit('subscribed') emit('subscribed')
isAwaitingStripeSubscription.value = false isAwaitingStripeSubscription.value = false
} }
@@ -122,9 +118,6 @@ watch(
const handleSubscribe = async () => { const handleSubscribe = async () => {
if (isCloud) { if (isCloud) {
useTelemetry()?.trackSubscription('subscribe_clicked') useTelemetry()?.trackSubscription('subscribe_clicked')
}
if (shouldUseStripePricing.value) {
isAwaitingStripeSubscription.value = true isAwaitingStripeSubscription.value = true
showSubscriptionDialog() showSubscriptionDialog()
return return

View File

@@ -16,9 +16,9 @@ import { useDialogService } from '@/services/dialogService'
import type { components, operations } from '@/types/comfyRegistryTypes' import type { components, operations } from '@/types/comfyRegistryTypes'
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher' import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
type CloudSubscriptionCheckoutResponse = { type CloudSubscriptionCheckoutResponse = NonNullable<
checkout_url: string operations['createCloudSubscriptionCheckout']['responses']['201']['content']['application/json']
} >
export type CloudSubscriptionStatusResponse = NonNullable< export type CloudSubscriptionStatusResponse = NonNullable<
operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json'] operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json']

View File

@@ -37,6 +37,5 @@ export type RemoteConfig = {
model_upload_button_enabled?: boolean model_upload_button_enabled?: boolean
asset_update_options_enabled?: boolean asset_update_options_enabled?: boolean
private_models_enabled?: boolean private_models_enabled?: boolean
subscription_tiers_enabled?: boolean
onboarding_survey_enabled?: boolean onboarding_survey_enabled?: boolean
} }

View File

@@ -3,7 +3,6 @@ import type { Component } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { isCloud } from '@/platform/distribution/types' import { isCloud } from '@/platform/distribution/types'
import type { SettingTreeNode } from '@/platform/settings/settingStore' import type { SettingTreeNode } from '@/platform/settings/settingStore'
import { useSettingStore } from '@/platform/settings/settingStore' import { useSettingStore } from '@/platform/settings/settingStore'
@@ -36,7 +35,6 @@ export function useSettingUI(
const { shouldRenderVueNodes } = useVueFeatureFlags() const { shouldRenderVueNodes } = useVueFeatureFlags()
const { isActiveSubscription } = useSubscription() const { isActiveSubscription } = useSubscription()
const { flags } = useFeatureFlags()
const settingRoot = computed<SettingTreeNode>(() => { const settingRoot = computed<SettingTreeNode>(() => {
const root = buildTree( const root = buildTree(
@@ -106,7 +104,6 @@ export function useSettingUI(
const shouldShowPlanCreditsPanel = computed(() => { const shouldShowPlanCreditsPanel = computed(() => {
if (!subscriptionPanel) return false if (!subscriptionPanel) return false
if (!flags.subscriptionTiersEnabled) return true
return isActiveSubscription.value return isActiveSubscription.value
}) })

View File

@@ -40,17 +40,7 @@
</div> </div>
<div v-if="isSubgraphNode" class="icon-[comfy--workflow] size-4" /> <div v-if="isSubgraphNode" class="icon-[comfy--workflow] size-4" />
<div <div v-if="isApiNode" class="icon-[lucide--component] size-4" />
v-if="isApiNode"
:class="
cn(
'size-4',
flags.subscriptionTiersEnabled
? 'icon-[lucide--component]'
: 'icon-[lucide--dollar-sign]'
)
"
/>
<!-- Node Title --> <!-- Node Title -->
<div <div
@@ -107,7 +97,6 @@ import EditableText from '@/components/common/EditableText.vue'
import Button from '@/components/ui/button/Button.vue' import Button from '@/components/ui/button/Button.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling' import { useErrorHandling } from '@/composables/useErrorHandling'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { st } from '@/i18n' import { st } from '@/i18n'
import { LGraphEventMode, RenderShape } from '@/lib/litegraph/src/litegraph' import { LGraphEventMode, RenderShape } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore' import { useSettingStore } from '@/platform/settings/settingStore'
@@ -138,8 +127,6 @@ const emit = defineEmits<{
'enter-subgraph': [] 'enter-subgraph': []
}>() }>()
const { flags } = useFeatureFlags()
// Error boundary implementation // Error boundary implementation
const renderError = ref<string | null>(null) const renderError = ref<string | null>(null)
const { toastErrorHandler } = useErrorHandling() const { toastErrorHandler } = useErrorHandling()

View File

@@ -1,8 +1,6 @@
import { describe, expect, vi } from 'vitest' import { describe, expect, vi } from 'vitest'
import { LGraphNode } from '@/lib/litegraph/src/litegraph' import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge'
import type { LGraphIcon } from '@/lib/litegraph/src/LGraphIcon'
import { subgraphTest } from '../../litegraph/subgraph/fixtures/subgraphFixtures' import { subgraphTest } from '../../litegraph/subgraph/fixtures/subgraphFixtures'
@@ -17,23 +15,10 @@ vi.mock('@/stores/workspace/colorPaletteStore', () => ({
}) })
})) }))
vi.mock('@/composables/useFeatureFlags', () => ({ const { updateSubgraphCredits, getCreditsBadge } = usePriceBadge()
useFeatureFlags: () => ({
flags: {
subscriptionTiersEnabled: false // Test legacy badge behavior
}
})
}))
const { updateSubgraphCredits } = usePriceBadge()
const mockNode = new LGraphNode('mock node') const mockNode = new LGraphNode('mock node')
const mockIcon: Partial<LGraphIcon> = { unicode: '\ue96b' } mockNode.badges = [getCreditsBadge('$0.05/Run')]
const badge: Partial<LGraphBadge> = {
icon: mockIcon as LGraphIcon,
text: '$0.05/Run'
}
mockNode.badges = [badge as LGraphBadge]
function getBadgeText(node: LGraphNode): string { function getBadgeText(node: LGraphNode): string {
const badge = node.badges[0] const badge = node.badges[0]