From 8d4a6df7f8bd159b57a6b3a98a3cf3a94ea01195 Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Wed, 3 Dec 2025 20:14:00 -0800 Subject: [PATCH] feat: add upgrade modal for model upload when private models disabled (#7124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 Co-authored-by: Alexander Brown --- src/composables/useFeatureFlags.ts | 10 +++- src/locales/en/main.json | 2 + .../components/UploadModelUpgradeModal.vue | 30 ++++++++++++ .../UploadModelUpgradeModalBody.vue | 9 ++++ .../UploadModelUpgradeModalFooter.vue | 34 ++++++++++++++ .../UploadModelUpgradeModalHeader.vue | 5 ++ .../assets/composables/useModelUpload.ts | 46 +++++++++++++------ src/platform/remoteConfig/types.ts | 1 + 8 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 src/platform/assets/components/UploadModelUpgradeModal.vue create mode 100644 src/platform/assets/components/UploadModelUpgradeModalBody.vue create mode 100644 src/platform/assets/components/UploadModelUpgradeModalFooter.vue create mode 100644 src/platform/assets/components/UploadModelUpgradeModalHeader.vue diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index de8c85912..b7d54b167 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -11,7 +11,8 @@ export enum ServerFeatureFlag { MAX_UPLOAD_SIZE = 'max_upload_size', MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4', 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' } /** @@ -47,6 +48,13 @@ export function useFeatureFlags() { false ) ) + }, + get privateModelsEnabled() { + // Check remote config first (from /api/features), fall back to websocket feature flags + return ( + remoteConfig.value.private_models_enabled ?? + api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false) + ) } }) diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 515d13f9e..cc2e62e9b 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -2121,6 +2121,8 @@ "modelUploaded": "Model imported! 🎉", "findInLibrary": "Find it in the {type} section of the models library.", "finish": "Finish", + "upgradeToUnlockFeature": "Upgrade to unlock this feature", + "upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.", "allModels": "All Models", "allCategory": "All {category}", "unknown": "Unknown", diff --git a/src/platform/assets/components/UploadModelUpgradeModal.vue b/src/platform/assets/components/UploadModelUpgradeModal.vue new file mode 100644 index 000000000..48010a3bb --- /dev/null +++ b/src/platform/assets/components/UploadModelUpgradeModal.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/platform/assets/components/UploadModelUpgradeModalBody.vue b/src/platform/assets/components/UploadModelUpgradeModalBody.vue new file mode 100644 index 000000000..16d7917a4 --- /dev/null +++ b/src/platform/assets/components/UploadModelUpgradeModalBody.vue @@ -0,0 +1,9 @@ + diff --git a/src/platform/assets/components/UploadModelUpgradeModalFooter.vue b/src/platform/assets/components/UploadModelUpgradeModalFooter.vue new file mode 100644 index 000000000..8439c16ec --- /dev/null +++ b/src/platform/assets/components/UploadModelUpgradeModalFooter.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/platform/assets/components/UploadModelUpgradeModalHeader.vue b/src/platform/assets/components/UploadModelUpgradeModalHeader.vue new file mode 100644 index 000000000..e09579d82 --- /dev/null +++ b/src/platform/assets/components/UploadModelUpgradeModalHeader.vue @@ -0,0 +1,5 @@ + diff --git a/src/platform/assets/composables/useModelUpload.ts b/src/platform/assets/composables/useModelUpload.ts index e765d5c55..86372473d 100644 --- a/src/platform/assets/composables/useModelUpload.ts +++ b/src/platform/assets/composables/useModelUpload.ts @@ -1,6 +1,8 @@ import { useFeatureFlags } from '@/composables/useFeatureFlags' import UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue' import UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue' +import UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue' +import UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' import { useDialogStore } from '@/stores/dialogStore' import type { UseAsyncStateReturn } from '@vueuse/core' @@ -14,22 +16,38 @@ export function useModelUpload( const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled) function showUploadDialog() { - dialogStore.showDialog({ - key: 'upload-model', - headerComponent: UploadModelDialogHeader, - component: UploadModelDialog, - props: { - onUploadSuccess: async () => { - await execute?.() + if (!flags.privateModelsEnabled) { + // Show upgrade modal if private models are disabled + dialogStore.showDialog({ + key: 'upload-model-upgrade', + headerComponent: UploadModelUpgradeModalHeader, + component: UploadModelUpgradeModal, + dialogComponentProps: { + pt: { + header: 'py-0! pl-0!', + content: 'p-0!' + } } - }, - dialogComponentProps: { - pt: { - header: 'py-0! pl-0!', - content: 'p-0!' + }) + } else { + // Show regular upload modal + dialogStore.showDialog({ + key: 'upload-model', + headerComponent: UploadModelDialogHeader, + component: UploadModelDialog, + props: { + onUploadSuccess: async () => { + await execute?.() + } + }, + dialogComponentProps: { + pt: { + header: 'py-0! pl-0!', + content: 'p-0!' + } } - } - }) + }) + } } return { isUploadButtonEnabled, showUploadDialog } } diff --git a/src/platform/remoteConfig/types.ts b/src/platform/remoteConfig/types.ts index 8d2c400f3..e5ad9fd33 100644 --- a/src/platform/remoteConfig/types.ts +++ b/src/platform/remoteConfig/types.ts @@ -36,4 +36,5 @@ export type RemoteConfig = { telemetry_disabled_events?: TelemetryEventName[] model_upload_button_enabled?: boolean asset_update_options_enabled?: boolean + private_models_enabled?: boolean }