Skip to content

Commit 8d4a6df

Browse files
luke-mino-altherrclaudeDrJKL
authored
feat: add upgrade modal for model upload when private models disabled (#7124)
## Summary Adds a dedicated upgrade modal that appears when users without private models access try to upload models, providing a clear path to upgrade their subscription. ## Changes - **New upgrade modal**: Created `UploadModelUpgradeModal` with dedicated body, header, and footer components - **Conditional rendering**: Modified `AssetBrowserModal` to show upgrade modal when `privateModelsEnabled` flag is false - **Subscription integration**: Connected upgrade flow to existing subscription system via `showSubscriptionDialog()` - **Localization**: Added localization keys for upgrade messaging ## Review Focus - Conditional logic in `AssetBrowserModal.handleUploadClick()` based on feature flags - Component naming consistency (all upgrade-related components prefixed with `UploadModelUpgrade`) - Footer component refactoring maintains existing upload wizard behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7124-feat-add-upgrade-modal-for-model-upload-when-private-models-disabled-2be6d73d36508147b72eea8a1d6ab772) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
1 parent 52e915b commit 8d4a6df

File tree

8 files changed

+122
-15
lines changed

8 files changed

+122
-15
lines changed

src/composables/useFeatureFlags.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export enum ServerFeatureFlag {
1111
MAX_UPLOAD_SIZE = 'max_upload_size',
1212
MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4',
1313
MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',
14-
ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled'
14+
ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled',
15+
PRIVATE_MODELS_ENABLED = 'private_models_enabled'
1516
}
1617

1718
/**
@@ -47,6 +48,13 @@ export function useFeatureFlags() {
4748
false
4849
)
4950
)
51+
},
52+
get privateModelsEnabled() {
53+
// Check remote config first (from /api/features), fall back to websocket feature flags
54+
return (
55+
remoteConfig.value.private_models_enabled ??
56+
api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)
57+
)
5058
}
5159
})
5260

src/locales/en/main.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,8 @@
21212121
"modelUploaded": "Model imported! 🎉",
21222122
"findInLibrary": "Find it in the {type} section of the models library.",
21232123
"finish": "Finish",
2124+
"upgradeToUnlockFeature": "Upgrade to unlock this feature",
2125+
"upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.",
21242126
"allModels": "All Models",
21252127
"allCategory": "All {category}",
21262128
"unknown": "Unknown",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<div
3+
class="flex flex-col justify-between gap-10 p-4 border-t border-border-default w-auto max-w-[min(500px,90vw)]"
4+
>
5+
<UploadModelUpgradeModalBody />
6+
7+
<UploadModelUpgradeModalFooter
8+
@close="handleClose"
9+
@subscribe="handleSubscribe"
10+
/>
11+
</div>
12+
</template>
13+
14+
<script setup lang="ts">
15+
import UploadModelUpgradeModalBody from '@/platform/assets/components/UploadModelUpgradeModalBody.vue'
16+
import UploadModelUpgradeModalFooter from '@/platform/assets/components/UploadModelUpgradeModalFooter.vue'
17+
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
18+
import { useDialogStore } from '@/stores/dialogStore'
19+
20+
const dialogStore = useDialogStore()
21+
const { showSubscriptionDialog } = useSubscription()
22+
23+
function handleClose() {
24+
dialogStore.closeDialog({ key: 'upload-model-upgrade' })
25+
}
26+
27+
function handleSubscribe() {
28+
showSubscriptionDialog()
29+
}
30+
</script>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<div
3+
class="flex flex-1 flex-col items-center justify-center text-base text-muted-foreground"
4+
>
5+
<p class="m-0 max-w-md">
6+
{{ $t('assetBrowser.upgradeFeatureDescription') }}
7+
</p>
8+
</div>
9+
</template>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<template>
2+
<div class="flex flex-wrap justify-end gap-2 w-full">
3+
<a
4+
href="https://blog.comfy.org/p/comfy-cloud-new-features-and-pricing"
5+
target="_blank"
6+
rel="noopener noreferrer"
7+
class="text-muted-foreground mr-auto underline flex items-center gap-2"
8+
>
9+
<i class="icon-[lucide--external-link]" />
10+
<span>{{ $t('g.learnMore') }}</span>
11+
</a>
12+
<TextButton
13+
:label="$t('g.close')"
14+
type="transparent"
15+
size="md"
16+
@click="emit('close')"
17+
/>
18+
<TextButton
19+
:label="$t('subscription.required.subscribe')"
20+
type="secondary"
21+
size="md"
22+
@click="emit('subscribe')"
23+
/>
24+
</div>
25+
</template>
26+
27+
<script setup lang="ts">
28+
import TextButton from '@/components/button/TextButton.vue'
29+
30+
const emit = defineEmits<{
31+
close: []
32+
subscribe: []
33+
}>()
34+
</script>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div class="flex items-center gap-2 p-4 font-bold">
3+
<span>{{ $t('assetBrowser.upgradeToUnlockFeature') }}</span>
4+
</div>
5+
</template>
Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useFeatureFlags } from '@/composables/useFeatureFlags'
22
import UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue'
33
import UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue'
4+
import UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue'
5+
import UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue'
46
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
57
import { useDialogStore } from '@/stores/dialogStore'
68
import type { UseAsyncStateReturn } from '@vueuse/core'
@@ -14,22 +16,38 @@ export function useModelUpload(
1416
const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled)
1517

1618
function showUploadDialog() {
17-
dialogStore.showDialog({
18-
key: 'upload-model',
19-
headerComponent: UploadModelDialogHeader,
20-
component: UploadModelDialog,
21-
props: {
22-
onUploadSuccess: async () => {
23-
await execute?.()
19+
if (!flags.privateModelsEnabled) {
20+
// Show upgrade modal if private models are disabled
21+
dialogStore.showDialog({
22+
key: 'upload-model-upgrade',
23+
headerComponent: UploadModelUpgradeModalHeader,
24+
component: UploadModelUpgradeModal,
25+
dialogComponentProps: {
26+
pt: {
27+
header: 'py-0! pl-0!',
28+
content: 'p-0!'
29+
}
2430
}
25-
},
26-
dialogComponentProps: {
27-
pt: {
28-
header: 'py-0! pl-0!',
29-
content: 'p-0!'
31+
})
32+
} else {
33+
// Show regular upload modal
34+
dialogStore.showDialog({
35+
key: 'upload-model',
36+
headerComponent: UploadModelDialogHeader,
37+
component: UploadModelDialog,
38+
props: {
39+
onUploadSuccess: async () => {
40+
await execute?.()
41+
}
42+
},
43+
dialogComponentProps: {
44+
pt: {
45+
header: 'py-0! pl-0!',
46+
content: 'p-0!'
47+
}
3048
}
31-
}
32-
})
49+
})
50+
}
3351
}
3452
return { isUploadButtonEnabled, showUploadDialog }
3553
}

src/platform/remoteConfig/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ export type RemoteConfig = {
3636
telemetry_disabled_events?: TelemetryEventName[]
3737
model_upload_button_enabled?: boolean
3838
asset_update_options_enabled?: boolean
39+
private_models_enabled?: boolean
3940
}

0 commit comments

Comments
 (0)