mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
set config via feature flags (#6590)
In cloud environment, allow all the config values to be set by the feature flag endpoint and be updated dynamically (on 30s poll). Retain origianl behavior for OSS. On cloud, config changes shouldn't be changed via redeploy and the promoted image should match the staging image. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6590-set-config-via-feature-flags-2a26d73d3650819f8084eb2695c51f22) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL0424@gmail.com>
This commit is contained in:
14
global.d.ts
vendored
14
global.d.ts
vendored
@@ -8,7 +8,21 @@ declare const __USE_PROD_CONFIG__: boolean
|
||||
interface Window {
|
||||
__CONFIG__: {
|
||||
mixpanel_token?: string
|
||||
require_whitelist?: boolean
|
||||
subscription_required?: boolean
|
||||
max_upload_size?: number
|
||||
comfy_api_base_url?: string
|
||||
comfy_platform_base_url?: string
|
||||
firebase_config?: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
databaseURL?: string
|
||||
projectId: string
|
||||
storageBucket: string
|
||||
messagingSenderId: string
|
||||
appId: string
|
||||
measurementId?: string
|
||||
}
|
||||
server_health_alert?: {
|
||||
message: string
|
||||
tooltip?: string
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<small class="text-center text-muted">
|
||||
{{ t('auth.apiKey.helpText') }}
|
||||
<a
|
||||
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
|
||||
:href="`${comfyPlatformBaseUrl}/login`"
|
||||
target="_blank"
|
||||
class="cursor-pointer text-blue-500"
|
||||
>
|
||||
@@ -145,11 +145,15 @@
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import Message from 'primevue/message'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
||||
import {
|
||||
configValueOrDefault,
|
||||
remoteConfig
|
||||
} from '@/platform/remoteConfig/remoteConfig'
|
||||
import type { SignInData, SignUpData } from '@/schemas/signInSchema'
|
||||
import { isHostWhitelisted, normalizeHost } from '@/utils/hostWhitelist'
|
||||
import { isInChina } from '@/utils/networkUtil'
|
||||
@@ -168,6 +172,13 @@ const isSecureContext = window.isSecureContext
|
||||
const isSignIn = ref(true)
|
||||
const showApiKeyForm = ref(false)
|
||||
const ssoAllowed = isHostWhitelisted(normalizeHost(window.location.hostname))
|
||||
const comfyPlatformBaseUrl = computed(() =>
|
||||
configValueOrDefault(
|
||||
remoteConfig.value,
|
||||
'comfy_platform_base_url',
|
||||
getComfyPlatformBaseUrl()
|
||||
)
|
||||
)
|
||||
|
||||
const toggleState = () => {
|
||||
isSignIn.value = !isSignIn.value
|
||||
|
||||
@@ -9,7 +9,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
||||
|
||||
import ApiKeyForm from './ApiKeyForm.vue'
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('ApiKeyForm', () => {
|
||||
const helpText = wrapper.find('small')
|
||||
expect(helpText.text()).toContain('Need an API key?')
|
||||
expect(helpText.find('a').attributes('href')).toBe(
|
||||
`${COMFY_PLATFORM_BASE_URL}/login`
|
||||
`${getComfyPlatformBaseUrl()}/login`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<small class="text-muted">
|
||||
{{ t('auth.apiKey.helpText') }}
|
||||
<a
|
||||
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
|
||||
:href="`${comfyPlatformBaseUrl}/login`"
|
||||
target="_blank"
|
||||
class="cursor-pointer text-blue-500"
|
||||
>
|
||||
@@ -88,7 +88,11 @@ import Message from 'primevue/message'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
||||
import {
|
||||
configValueOrDefault,
|
||||
remoteConfig
|
||||
} from '@/platform/remoteConfig/remoteConfig'
|
||||
import { apiKeySchema } from '@/schemas/signInSchema'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
@@ -96,6 +100,13 @@ import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const apiKeyStore = useApiKeyAuthStore()
|
||||
const loading = computed(() => authStore.loading)
|
||||
const comfyPlatformBaseUrl = computed(() =>
|
||||
configValueOrDefault(
|
||||
remoteConfig.value,
|
||||
'comfy_platform_base_url',
|
||||
getComfyPlatformBaseUrl()
|
||||
)
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
||||
@@ -1,7 +1,43 @@
|
||||
export const COMFY_API_BASE_URL = __USE_PROD_CONFIG__
|
||||
? 'https://api.comfy.org'
|
||||
: 'https://stagingapi.comfy.org'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import {
|
||||
configValueOrDefault,
|
||||
remoteConfig
|
||||
} from '@/platform/remoteConfig/remoteConfig'
|
||||
|
||||
export const COMFY_PLATFORM_BASE_URL = __USE_PROD_CONFIG__
|
||||
? 'https://platform.comfy.org'
|
||||
: 'https://stagingplatform.comfy.org'
|
||||
const PROD_API_BASE_URL = 'https://api.comfy.org'
|
||||
const STAGING_API_BASE_URL = 'https://stagingapi.comfy.org'
|
||||
|
||||
const PROD_PLATFORM_BASE_URL = 'https://platform.comfy.org'
|
||||
const STAGING_PLATFORM_BASE_URL = 'https://stagingplatform.comfy.org'
|
||||
|
||||
const BUILD_TIME_API_BASE_URL = __USE_PROD_CONFIG__
|
||||
? PROD_API_BASE_URL
|
||||
: STAGING_API_BASE_URL
|
||||
|
||||
const BUILD_TIME_PLATFORM_BASE_URL = __USE_PROD_CONFIG__
|
||||
? PROD_PLATFORM_BASE_URL
|
||||
: STAGING_PLATFORM_BASE_URL
|
||||
|
||||
export function getComfyApiBaseUrl(): string {
|
||||
if (!isCloud) {
|
||||
return BUILD_TIME_API_BASE_URL
|
||||
}
|
||||
|
||||
return configValueOrDefault(
|
||||
remoteConfig.value,
|
||||
'comfy_api_base_url',
|
||||
BUILD_TIME_API_BASE_URL
|
||||
)
|
||||
}
|
||||
|
||||
export function getComfyPlatformBaseUrl(): string {
|
||||
if (!isCloud) {
|
||||
return BUILD_TIME_PLATFORM_BASE_URL
|
||||
}
|
||||
|
||||
return configValueOrDefault(
|
||||
remoteConfig.value,
|
||||
'comfy_platform_base_url',
|
||||
BUILD_TIME_PLATFORM_BASE_URL
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { FirebaseOptions } from 'firebase/app'
|
||||
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
|
||||
const DEV_CONFIG: FirebaseOptions = {
|
||||
apiKey: 'AIzaSyDa_YMeyzV0SkVe92vBZ1tVikWBmOU5KVE',
|
||||
authDomain: 'dreamboothy-dev.firebaseapp.com',
|
||||
@@ -22,7 +25,18 @@ const PROD_CONFIG: FirebaseOptions = {
|
||||
measurementId: 'G-3ZBD3MBTG4'
|
||||
}
|
||||
|
||||
// To test with prod config while using dev server, set USE_PROD_CONFIG=true in .env
|
||||
export const FIREBASE_CONFIG: FirebaseOptions = __USE_PROD_CONFIG__
|
||||
? PROD_CONFIG
|
||||
: DEV_CONFIG
|
||||
const BUILD_TIME_CONFIG = __USE_PROD_CONFIG__ ? PROD_CONFIG : DEV_CONFIG
|
||||
|
||||
/**
|
||||
* Returns the Firebase configuration for the current environment.
|
||||
* - Cloud builds use runtime configuration delivered via feature flags
|
||||
* - OSS / localhost builds fall back to the build-time config determined by __USE_PROD_CONFIG__
|
||||
*/
|
||||
export function getFirebaseConfig(): FirebaseOptions {
|
||||
if (!isCloud) {
|
||||
return BUILD_TIME_CONFIG
|
||||
}
|
||||
|
||||
const runtimeConfig = remoteConfig.value.firebase_config
|
||||
return runtimeConfig ?? BUILD_TIME_CONFIG
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Tooltip from 'primevue/tooltip'
|
||||
import { createApp } from 'vue'
|
||||
import { VueFire, VueFireAuth } from 'vuefire'
|
||||
|
||||
import { FIREBASE_CONFIG } from '@/config/firebase'
|
||||
import { getFirebaseConfig } from '@/config/firebase'
|
||||
import '@/lib/litegraph/public/css/litegraph.css'
|
||||
import router from '@/router'
|
||||
|
||||
@@ -40,7 +40,7 @@ const ComfyUIPreset = definePreset(Aura, {
|
||||
}
|
||||
})
|
||||
|
||||
const firebaseApp = initializeApp(FIREBASE_CONFIG)
|
||||
const firebaseApp = initializeApp(getFirebaseConfig())
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createSharedComposable } from '@vueuse/core'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
||||
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
|
||||
import { t } from '@/i18n'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
@@ -74,6 +74,8 @@ function useSubscriptionInternal() {
|
||||
() => `$${MONTHLY_SUBSCRIPTION_PRICE.toFixed(0)}`
|
||||
)
|
||||
|
||||
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
||||
|
||||
const fetchStatus = wrapWithErrorHandlingAsync(
|
||||
fetchSubscriptionStatus,
|
||||
reportError
|
||||
@@ -114,7 +116,7 @@ function useSubscriptionInternal() {
|
||||
}
|
||||
|
||||
const handleViewUsageHistory = () => {
|
||||
window.open('https://platform.comfy.org/profile/usage', '_blank')
|
||||
window.open(`${getComfyPlatformBaseUrl()}/profile/usage`, '_blank')
|
||||
}
|
||||
|
||||
const handleLearnMore = () => {
|
||||
@@ -136,7 +138,7 @@ function useSubscriptionInternal() {
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${COMFY_API_BASE_URL}/customers/cloud-subscription-status`,
|
||||
buildApiUrl('/customers/cloud-subscription-status'),
|
||||
{
|
||||
headers: {
|
||||
...authHeader,
|
||||
@@ -181,7 +183,7 @@ function useSubscriptionInternal() {
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${COMFY_API_BASE_URL}/customers/cloud-subscription-checkout`,
|
||||
buildApiUrl('/customers/cloud-subscription-checkout'),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -21,6 +21,15 @@ import type { RemoteConfig } from './types'
|
||||
*/
|
||||
export const remoteConfig = ref<RemoteConfig>({})
|
||||
|
||||
export function configValueOrDefault<K extends keyof RemoteConfig>(
|
||||
remoteConfig: RemoteConfig,
|
||||
key: K,
|
||||
defaultValue: NonNullable<RemoteConfig[K]>
|
||||
): NonNullable<RemoteConfig[K]> {
|
||||
const configValue = remoteConfig[key]
|
||||
return configValue || defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads remote configuration from the backend /api/features endpoint
|
||||
* and updates the reactive remoteConfig ref
|
||||
|
||||
@@ -8,6 +8,17 @@ type ServerHealthAlert = {
|
||||
badge?: string
|
||||
}
|
||||
|
||||
type FirebaseRuntimeConfig = {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
databaseURL?: string
|
||||
projectId: string
|
||||
storageBucket: string
|
||||
messagingSenderId: string
|
||||
appId: string
|
||||
measurementId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote configuration type
|
||||
* Configuration fetched from the server at runtime
|
||||
@@ -16,4 +27,8 @@ export type RemoteConfig = {
|
||||
mixpanel_token?: string
|
||||
subscription_required?: boolean
|
||||
server_health_alert?: ServerHealthAlert
|
||||
max_upload_size?: number
|
||||
comfy_api_base_url?: string
|
||||
comfy_platform_base_url?: string
|
||||
firebase_config?: FirebaseRuntimeConfig
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import type { AxiosError, AxiosResponse } from 'axios'
|
||||
import axios from 'axios'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import type { components, operations } from '@/types/comfyRegistryTypes'
|
||||
import { isAbortError } from '@/utils/typeGuardUtil'
|
||||
|
||||
const releaseApiClient = axios.create({
|
||||
baseURL: COMFY_API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// Use generated types from OpenAPI spec
|
||||
export type ReleaseNote = components['schemas']['ReleaseNote']
|
||||
type GetReleasesParams = operations['getReleaseNotes']['parameters']['query']
|
||||
@@ -20,11 +13,25 @@ type GetReleasesParams = operations['getReleaseNotes']['parameters']['query']
|
||||
// Use generated error response type
|
||||
type ErrorResponse = components['schemas']['ErrorResponse']
|
||||
|
||||
const releaseApiClient = axios.create({
|
||||
baseURL: getComfyApiBaseUrl(),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// Release service for fetching release notes
|
||||
export const useReleaseService = () => {
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
watch(
|
||||
() => getComfyApiBaseUrl(),
|
||||
(url) => {
|
||||
releaseApiClient.defaults.baseURL = url
|
||||
}
|
||||
)
|
||||
|
||||
// No transformation needed - API response matches the generated type
|
||||
|
||||
// Handle API errors with context
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { AxiosError, AxiosResponse } from 'axios'
|
||||
import axios from 'axios'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import type { components, operations } from '@/types/comfyRegistryTypes'
|
||||
import { isAbortError } from '@/utils/typeGuardUtil'
|
||||
@@ -24,7 +24,7 @@ type CustomerEventsResponseQuery =
|
||||
export type AuditLog = components['schemas']['AuditLog']
|
||||
|
||||
const customerApiClient = axios.create({
|
||||
baseURL: COMFY_API_BASE_URL,
|
||||
baseURL: getComfyApiBaseUrl(),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
@@ -35,6 +35,13 @@ export const useCustomerEventsService = () => {
|
||||
const error = ref<string | null>(null)
|
||||
const { d } = useI18n()
|
||||
|
||||
watch(
|
||||
() => getComfyApiBaseUrl(),
|
||||
(url) => {
|
||||
customerApiClient.defaults.baseURL = url
|
||||
}
|
||||
)
|
||||
|
||||
const handleRequestError = (
|
||||
err: unknown,
|
||||
context: string,
|
||||
|
||||
@@ -21,7 +21,7 @@ import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useFirebaseAuth } from 'vuefire'
|
||||
|
||||
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
|
||||
import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import { t } from '@/i18n'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
@@ -65,6 +65,8 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
// Token refresh trigger - increments when token is refreshed
|
||||
const tokenRefreshTrigger = ref(0)
|
||||
|
||||
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
||||
|
||||
// Providers
|
||||
const googleProvider = new GoogleAuthProvider()
|
||||
googleProvider.addScope('email')
|
||||
@@ -163,7 +165,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch(`${COMFY_API_BASE_URL}/customers/balance`, {
|
||||
const response = await fetch(buildApiUrl('/customers/balance'), {
|
||||
headers: {
|
||||
...authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
@@ -199,7 +201,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
|
||||
}
|
||||
|
||||
const createCustomerRes = await fetch(`${COMFY_API_BASE_URL}/customers`, {
|
||||
const createCustomerRes = await fetch(buildApiUrl('/customers'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authHeader,
|
||||
@@ -367,7 +369,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
customerCreated.value = true
|
||||
}
|
||||
|
||||
const response = await fetch(`${COMFY_API_BASE_URL}/customers/credit`, {
|
||||
const response = await fetch(buildApiUrl('/customers/credit'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authHeader,
|
||||
@@ -401,7 +403,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
|
||||
}
|
||||
|
||||
const response = await fetch(`${COMFY_API_BASE_URL}/customers/billing`, {
|
||||
const response = await fetch(buildApiUrl('/customers/billing'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...authHeader,
|
||||
|
||||
@@ -283,7 +283,7 @@ describe('useSubscription', () => {
|
||||
handleViewUsageHistory()
|
||||
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith(
|
||||
'https://platform.comfy.org/profile/usage',
|
||||
'https://stagingplatform.comfy.org/profile/usage',
|
||||
'_blank'
|
||||
)
|
||||
|
||||
|
||||
@@ -6,8 +6,27 @@ declare global {
|
||||
interface Window {
|
||||
__CONFIG__: {
|
||||
mixpanel_token?: string
|
||||
require_whitelist?: boolean
|
||||
subscription_required?: boolean
|
||||
server_health_alert?: string
|
||||
max_upload_size?: number
|
||||
comfy_api_base_url?: string
|
||||
comfy_platform_base_url?: string
|
||||
firebase_config?: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
databaseURL?: string
|
||||
projectId: string
|
||||
storageBucket: string
|
||||
messagingSenderId: string
|
||||
appId: string
|
||||
measurementId?: string
|
||||
}
|
||||
server_health_alert?: {
|
||||
message: string
|
||||
tooltip?: string
|
||||
severity?: 'info' | 'warning' | 'error'
|
||||
badge?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +43,17 @@ globalThis.__DISTRIBUTION__ = 'localhost'
|
||||
// Define runtime config for tests
|
||||
window.__CONFIG__ = {
|
||||
subscription_required: true,
|
||||
mixpanel_token: 'test-token'
|
||||
mixpanel_token: 'test-token',
|
||||
comfy_api_base_url: 'https://stagingapi.comfy.org',
|
||||
comfy_platform_base_url: 'https://stagingplatform.comfy.org',
|
||||
firebase_config: {
|
||||
apiKey: 'test',
|
||||
authDomain: 'test.firebaseapp.com',
|
||||
projectId: 'test',
|
||||
storageBucket: 'test.appspot.com',
|
||||
messagingSenderId: '123',
|
||||
appId: '123'
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Worker for extendable-media-recorder
|
||||
|
||||
Reference in New Issue
Block a user