From aa5223ad7303b4ce6bd0c59d8ccb41212d7fdf68 Mon Sep 17 00:00:00 2001 From: Lulu Date: Wed, 10 Jun 2026 16:36:31 +0800 Subject: [PATCH] feat(stage): hide BYOK providers for Steam builds via VITE_DISABLE_CUSTOM_PROVIDERS (#1908) --- .../components/CardCreationDialog.vue | 9 ++- .../src/pages/settings/modules/artistry.vue | 33 ++++++---- .../src/pages/settings/providers/index.vue | 65 ++++++++++--------- packages/stage-shared/src/env-vars.ts | 4 ++ packages/stage-ui/src/stores/providers.ts | 12 +++- vite-env.d.ts | 1 + 6 files changed, 75 insertions(+), 49 deletions(-) diff --git a/packages/stage-pages/src/pages/settings/airi-card/components/CardCreationDialog.vue b/packages/stage-pages/src/pages/settings/airi-card/components/CardCreationDialog.vue index fc929ab90..2f2647787 100644 --- a/packages/stage-pages/src/pages/settings/airi-card/components/CardCreationDialog.vue +++ b/packages/stage-pages/src/pages/settings/airi-card/components/CardCreationDialog.vue @@ -4,6 +4,7 @@ import type { AiriExtension } from '@proj-airi/stage-ui/stores/modules/airi-card import kebabcase from '@stdlib/string-base-kebabcase' +import { isCustomProvidersDisabled } from '@proj-airi/stage-shared' import { DEFAULT_ARTISTRY_WIDGET_INSTRUCTION } from '@proj-airi/stage-ui/constants/prompts/artistry-instruction' import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models' import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card' @@ -155,9 +156,13 @@ const speechVoiceOptions = computed(() => { const artistryProviderOptions = computed(() => { return [ { value: 'none', label: 'None (Disabled)' }, - { value: 'replicate', label: 'Replicate' }, { value: 'comfyui', label: 'ComfyUI' }, - { value: 'nanobanana', label: 'Nano Banana' }, + ...(isCustomProvidersDisabled() + ? [] + : [ + { value: 'replicate', label: 'Replicate' }, + { value: 'nanobanana', label: 'Nano Banana' }, + ]), ] }) diff --git a/packages/stage-pages/src/pages/settings/modules/artistry.vue b/packages/stage-pages/src/pages/settings/modules/artistry.vue index e5d1802d7..24eb1b3c4 100644 --- a/packages/stage-pages/src/pages/settings/modules/artistry.vue +++ b/packages/stage-pages/src/pages/settings/modules/artistry.vue @@ -1,4 +1,5 @@ diff --git a/packages/stage-pages/src/pages/settings/providers/index.vue b/packages/stage-pages/src/pages/settings/providers/index.vue index a0344dcc7..d3ba78de7 100644 --- a/packages/stage-pages/src/pages/settings/providers/index.vue +++ b/packages/stage-pages/src/pages/settings/providers/index.vue @@ -2,6 +2,7 @@ import type { ProviderSourceDeployment, ProviderSourcePricing } from '@proj-airi/stage-ui/libs/providers/source-metadata' import type { Ref } from 'vue' +import { isCustomProvidersDisabled } from '@proj-airi/stage-shared' import { IconStatusItem, RippleGrid } from '@proj-airi/stage-ui/components' import { useAnalytics } from '@proj-airi/stage-ui/composables' import { useRippleGridState } from '@proj-airi/stage-ui/composables/use-ripple-grid-state' @@ -69,36 +70,40 @@ const allArtistryProvidersMetadata = computed(() => { beginnerRecommended: true, iconImage: undefined, }, - { - id: 'replicate', - category: 'artistry', - icon: 'i-lobe-icons:replicate', - iconColor: 'i-lobe-icons:replicate-color', - name: 'Replicate', - localizedName: 'Replicate', - description: t('settings.pages.providers.categories.artistry.items.replicate.description'), - localizedDescription: t('settings.pages.providers.categories.artistry.items.replicate.description'), - configured: !!artistryStore.replicateApiKey, - to: '/settings/providers/artistry/replicate', - pricing: 'paid', - deployment: 'cloud', - iconImage: undefined, - }, - { - id: 'nanobanana', - category: 'artistry', - icon: 'i-solar:gallery-round-bold-duotone', - iconColor: 'text-amber-500', - name: 'Nano Banana', - localizedName: 'Nano Banana', - description: t('settings.pages.providers.categories.artistry.items.nanobanana.description'), - localizedDescription: t('settings.pages.providers.categories.artistry.items.nanobanana.description'), - configured: !!artistryStore.nanobananaApiKey, - to: '/settings/providers/artistry/nanobanana', - pricing: 'free', - deployment: 'cloud', - iconImage: undefined, - }, + ...(isCustomProvidersDisabled() + ? [] + : [ + { + id: 'replicate', + category: 'artistry', + icon: 'i-lobe-icons:replicate', + iconColor: 'i-lobe-icons:replicate-color', + name: 'Replicate', + localizedName: 'Replicate', + description: t('settings.pages.providers.categories.artistry.items.replicate.description'), + localizedDescription: t('settings.pages.providers.categories.artistry.items.replicate.description'), + configured: !!artistryStore.replicateApiKey, + to: '/settings/providers/artistry/replicate', + pricing: 'paid', + deployment: 'cloud', + iconImage: undefined, + }, + { + id: 'nanobanana', + category: 'artistry', + icon: 'i-solar:gallery-round-bold-duotone', + iconColor: 'text-amber-500', + name: 'Nano Banana', + localizedName: 'Nano Banana', + description: t('settings.pages.providers.categories.artistry.items.nanobanana.description'), + localizedDescription: t('settings.pages.providers.categories.artistry.items.nanobanana.description'), + configured: !!artistryStore.nanobananaApiKey, + to: '/settings/providers/artistry/nanobanana', + pricing: 'free', + deployment: 'cloud', + iconImage: undefined, + }, + ]), ] }) diff --git a/packages/stage-shared/src/env-vars.ts b/packages/stage-shared/src/env-vars.ts index 894c64024..0a391dfb4 100644 --- a/packages/stage-shared/src/env-vars.ts +++ b/packages/stage-shared/src/env-vars.ts @@ -12,3 +12,7 @@ export function isEnvTruthy(value: string | undefined | null): boolean { export function isFluxPurchaseDisabled(): boolean { return isEnvTruthy(import.meta.env.VITE_DISABLE_FLUX_PURCHASE) } + +export function isCustomProvidersDisabled(): boolean { + return isEnvTruthy(import.meta.env.VITE_DISABLE_CUSTOM_PROVIDERS) +} diff --git a/packages/stage-ui/src/stores/providers.ts b/packages/stage-ui/src/stores/providers.ts index 4f676f177..75b599f19 100644 --- a/packages/stage-ui/src/stores/providers.ts +++ b/packages/stage-ui/src/stores/providers.ts @@ -24,7 +24,7 @@ import type { ProviderOnboardingField } from '../libs/providers/types' import type { AliyunRealtimeSpeechExtraOptions } from './providers/aliyun/stream-transcription' import { errorMessageFrom } from '@moeru/std' -import { isStageTamagotchi, isUrl } from '@proj-airi/stage-shared' +import { isCustomProvidersDisabled, isStageTamagotchi, isUrl } from '@proj-airi/stage-shared' import { getCachedWebGPUCapabilities, isWebGPUSupported } from '@proj-airi/stage-shared/webgpu' import { computedAsync, useIntervalFn, useLocalStorage } from '@vueuse/core' import { @@ -283,6 +283,7 @@ export const useProvidersStore = defineStore('providers', () => { descriptionKey: 'settings.pages.providers.provider.speech-noop.description', description: 'No speech output.', icon: 'i-solar:volume-cross-bold-duotone', + requiresCredentials: false, defaultOptions: () => ({}), createProvider: async () => ({ speech: () => ({ @@ -857,6 +858,7 @@ export const useProvidersStore = defineStore('providers', () => { descriptionKey: 'settings.pages.providers.provider.browser-web-speech-api.description', description: 'Browser-native speech recognition. No API keys.', icon: 'i-solar:microphone-bold-duotone', + requiresCredentials: false, defaultOptions: () => ({ language: 'en-US', continuous: true, @@ -2033,6 +2035,7 @@ export const useProvidersStore = defineStore('providers', () => { descriptionKey: 'settings.pages.providers.provider.kokoro-local.description', description: 'Local text-to-speech using Kokoro-82M.', icon: 'i-lobe-icons:speaker', + requiresCredentials: false, defaultOptions: () => { const capabilities = getCachedWebGPUCapabilities() @@ -2744,8 +2747,11 @@ export const useProvidersStore = defineStore('providers', () => { if (overrides[provider.id] === false) continue - const p = getProviderMetadata(provider.id) - const isAvailableBy = p.isAvailableBy || (() => true) + const metadata = getProviderMetadata(provider.id) + if (isCustomProvidersDisabled() && metadata.requiresCredentials !== false) + continue + + const isAvailableBy = metadata.isAvailableBy || (() => true) const isAvailable = await isAvailableBy() if (isAvailable) { diff --git a/vite-env.d.ts b/vite-env.d.ts index 6a644979e..11dc7cd1d 100644 --- a/vite-env.d.ts +++ b/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_DISABLE_FLUX_PURCHASE?: string + readonly VITE_DISABLE_CUSTOM_PROVIDERS?: string readonly VITE_ENABLE_POSTHOG?: string readonly VITE_POSTHOG_PROJECT_KEY_WEB?: string readonly VITE_POSTHOG_PROJECT_KEY_DESKTOP?: string