From 49e0ee8e9e58d3c559f1353f85e27873bdec2408 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Tue, 31 Mar 2026 18:51:30 +0530 Subject: [PATCH] Consolidate on ALWRITY_ENABLED_FEATURES - remove all legacy support Backend: - Remove all legacy env var fallbacks (ALWRITY_FEATURE_PROFILE, ALWRITY_ROUTER_PROFILE, etc) - Remove get_active_profile() from start_alwrity_backend.py - Remove _env_flag_enabled() from app.py - Use ALWRITY_ENABLED_FEATURES as single source of truth Frontend: - demoMode.ts now uses only REACT_APP_ENABLED_FEATURES - Removed all legacy fallback keys (app_mode, demo_mode, podcast_only_demo_mode) Usage: ALWRITY_ENABLED_FEATURES=podcast # Podcast only ALWRITY_ENABLED_FEATURES=all # All features (default) --- backend/alwrity_utils/feature_profiles.py | 24 +++++---- backend/alwrity_utils/router_manager.py | 2 - backend/app.py | 28 ++--------- backend/start_alwrity_backend.py | 23 +++------ frontend/src/utils/demoMode.ts | 59 ++++++++--------------- 5 files changed, 42 insertions(+), 94 deletions(-) diff --git a/backend/alwrity_utils/feature_profiles.py b/backend/alwrity_utils/feature_profiles.py index 59fb7fdb..b10d6fd7 100644 --- a/backend/alwrity_utils/feature_profiles.py +++ b/backend/alwrity_utils/feature_profiles.py @@ -9,10 +9,8 @@ from typing import Iterable, Tuple from .feature_registry import FEATURE_GROUPS, PROFILE_GROUP_MAP -# Consolidated env var - supports both old and new format -ENV_FEATURE_PROFILE = "ALWRITY_ENABLED_FEATURES" -ENV_FEATURE_PROFILE_LEGACY = "ALWRITY_FEATURE_TO_ENABLE" -DEFAULT_PROFILE = "all" +ENV_ENABLED_FEATURES = "ALWRITY_ENABLED_FEATURES" +DEFAULT_FEATURES = "all" @dataclass(frozen=True) @@ -24,31 +22,31 @@ class ExpandedFeatureProfile: class UnknownFeatureProfileError(ValueError): - """Raised when ALWRITY_ENABLED_FEATURES contains unknown profile values.""" + """Raised when ALWRITY_ENABLED_FEATURES contains unknown feature values.""" def _get_env_value() -> str: - """Get the feature profile value from environment - new var takes precedence.""" - return os.getenv(ENV_FEATURE_PROFILE) or os.getenv(ENV_FEATURE_PROFILE_LEGACY) or DEFAULT_PROFILE + """Get the enabled features value from environment.""" + return os.getenv(ENV_ENABLED_FEATURES) or DEFAULT_FEATURES def _normalize_values(raw_value: str | None) -> Tuple[str, ...]: if not raw_value or not raw_value.strip(): - return (DEFAULT_PROFILE,) + return (DEFAULT_FEATURES,) normalized = tuple( value.strip().lower() for value in raw_value.split(",") if value.strip() ) - return normalized or (DEFAULT_PROFILE,) + return normalized or (DEFAULT_FEATURES,) def parse_feature_profiles(raw_value: str | None = None) -> Tuple[str, ...]: - """Parse and validate profile names from env/raw input. + """Parse and validate feature names from env/raw input. - Supports comma-separated profile names, e.g. `core,podcast`. - Raises UnknownFeatureProfileError when any profile is not registered. + Supports comma-separated feature names, e.g. `podcast,core`. + Raises UnknownFeatureProfileError when any feature is not registered. """ selected_profiles = _normalize_values(raw_value if raw_value is not None else _get_env_value()) @@ -58,7 +56,7 @@ def parse_feature_profiles(raw_value: str | None = None) -> Tuple[str, ...]: supported = ", ".join(sorted(set(PROFILE_GROUP_MAP.keys()) | set(FEATURE_GROUPS.keys()))) unknown_display = ", ".join(unknown) raise UnknownFeatureProfileError( - f"Unknown {ENV_FEATURE_PROFILE} value(s): {unknown_display}. Supported profiles: {supported}." + f"Unknown {ENV_ENABLED_FEATURES} value(s): {unknown_display}. Supported: {supported}." ) return selected_profiles diff --git a/backend/alwrity_utils/router_manager.py b/backend/alwrity_utils/router_manager.py index 876262f6..363bc486 100644 --- a/backend/alwrity_utils/router_manager.py +++ b/backend/alwrity_utils/router_manager.py @@ -89,8 +89,6 @@ class RouterManager: - "all" - enable all features (default) - comma-separated: "podcast,blog-writer,youtube" - single feature: "podcast" - - DEPRECATED: ALWRITY_FEATURE_PROFILE, ALWRITY_ROUTER_PROFILE, ALWRITY_FEATURE_TO_ENABLE """ env_value = os.getenv("ALWRITY_ENABLED_FEATURES", "all").strip().lower() diff --git a/backend/app.py b/backend/app.py index b61a57da..a76b06e0 100644 --- a/backend/app.py +++ b/backend/app.py @@ -49,16 +49,6 @@ load_dotenv(project_root / '.env') # root .env (fallback) load_dotenv() # CWD .env (fallback) -def _env_flag_enabled(*env_names: str) -> bool: - """Return True when any provided env var is set to a truthy value.""" - truthy_values = {"1", "true", "yes", "on"} - for env_name in env_names: - value = os.getenv(env_name) - if value and value.strip().lower() in truthy_values: - return True - return False - - def get_enabled_features() -> set: """Get enabled features from ALWRITY_ENABLED_FEATURES env var. @@ -66,8 +56,6 @@ def get_enabled_features() -> set: - "all" - enable all features (default) - comma-separated: "podcast,core" - single feature: "podcast" - - DEPRECATED: ALWRITY_FEATURE_PROFILE, ALWRITY_ROUTER_PROFILE, ALWRITY_FEATURE_TO_ENABLE """ env_value = os.getenv("ALWRITY_ENABLED_FEATURES", "all").strip().lower() @@ -78,26 +66,18 @@ def get_enabled_features() -> set: def is_podcast_only_demo_mode() -> bool: - """Check if podcast-only mode is enabled via new or legacy flags.""" - # First check the new consolidated flag + """Check if podcast-only mode is enabled.""" enabled = get_enabled_features() - if "podcast" in enabled and "all" not in enabled: - return True - - # Fall back to legacy flags for backwards compatibility - return _env_flag_enabled( - "ALWRITY_PODCAST_ONLY_DEMO_MODE", - "PODCAST_ONLY_DEMO_MODE" - ) + return "podcast" in enabled and "all" not in enabled def should_include_non_podcast_features() -> bool: """Check if non-podcast features should be included.""" enabled = get_enabled_features() - return "all" in enabled or "core" in enabled or "blog-writer" in enabled + return "all" in enabled or "core" in enabled -# Legacy constant for backwards compatibility - prefer using get_enabled_features() +# Legacy constant for backwards compatibility PODCAST_ONLY_DEMO_MODE = is_podcast_only_demo_mode() diff --git a/backend/start_alwrity_backend.py b/backend/start_alwrity_backend.py index 95c67e5a..965dfcb0 100644 --- a/backend/start_alwrity_backend.py +++ b/backend/start_alwrity_backend.py @@ -33,8 +33,6 @@ def get_enabled_features() -> set: - "all" - enable all features (default) - comma-separated: "podcast,blog-writer,youtube" - single feature: "podcast" - - DEPRECATED: ALWRITY_FEATURE_PROFILE, ALWRITY_ROUTER_PROFILE, ALWRITY_FEATURE_TO_ENABLE """ env_value = os.getenv("ALWRITY_ENABLED_FEATURES", "all").strip().lower() @@ -44,14 +42,6 @@ def get_enabled_features() -> set: return {f.strip() for f in env_value.split(",") if f.strip()} -def get_active_profile() -> str: - """Legacy function - use get_enabled_features() instead.""" - enabled = get_enabled_features() - if "all" in enabled: - return "all" - return list(enabled)[0] if enabled else "all" - - def should_bootstrap_linguistic_models() -> bool: """Decide whether to bootstrap linguistic models based on enabled features.""" enabled_features = get_enabled_features() @@ -220,10 +210,11 @@ def bootstrap_local_llm_models() -> BootstrapResult: BOOTSTRAP_RESULTS = [] if __name__ == "__main__": - profile = get_active_profile() - os.environ["ALWRITY_ACTIVE_PROFILE"] = profile + enabled_features = get_enabled_features() + features_str = ",".join(sorted(enabled_features)) + os.environ["ALWRITY_ENABLED_FEATURES"] = features_str - print(f"\n📋 Active profile: {profile}") + print(f"\n📋 Enabled features: {features_str}") if should_bootstrap_linguistic_models(): result = bootstrap_linguistic_models() @@ -240,11 +231,11 @@ if __name__ == "__main__": else: verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" if verbose: - print("⏭️ Skipping local LLM model bootstrap (profile-gated)") - BOOTSTRAP_RESULTS.append(BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="profile_gated")) + print("⏭️ Skipping local LLM model bootstrap (feature-gated)") + BOOTSTRAP_RESULTS.append(BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="feature_gated")) summary = { - "active_profile": profile, + "enabled_features": features_str, "bootstraps": [asdict(r) for r in BOOTSTRAP_RESULTS] } os.environ["ALWRITY_BOOTSTRAP_SUMMARY"] = json.dumps(summary) diff --git a/frontend/src/utils/demoMode.ts b/frontend/src/utils/demoMode.ts index 0c5dfc65..cd24ce09 100644 --- a/frontend/src/utils/demoMode.ts +++ b/frontend/src/utils/demoMode.ts @@ -1,55 +1,36 @@ /** * Consolidated feature mode detection utilities. * - * Primary: REACT_APP_ENABLED_FEATURES (format: "all" or "podcast,core") - * - * DEPRECATED (fallback order): - * - REACT_APP_APP_MODE - * - REACT_APP_DEMO_MODE - * - REACT_APP_PODCAST_ONLY_DEMO_MODE + * Primary env var: REACT_APP_ENABLED_FEATURES + * Format: "all" or comma-separated: "podcast,core" */ -const ENABLED_FEATURES_STORAGE_KEYS = [ - 'enabled_features', // Primary - 'app_mode', - 'demo_mode', - 'podcast_only_demo_mode', -]; - -const ENABLED_FEATURES_ENV_KEYS = [ - 'REACT_APP_ENABLED_FEATURES', // Primary - use this! - 'REACT_APP_APP_MODE', // DEPRECATED - 'REACT_APP_DEMO_MODE', // DEPRECATED - 'REACT_APP_PODCAST_ONLY_DEMO_MODE', // DEPRECATED -]; +const PRIMARY_STORAGE_KEY = 'enabled_features'; +const PRIMARY_ENV_KEY = 'REACT_APP_ENABLED_FEATURES'; /** * Get enabled features from localStorage or environment. - * Returns a set of enabled feature names. + * Returns a Set of enabled feature names. */ export function getEnabledFeatures(): Set { // Check localStorage first - for (const key of ENABLED_FEATURES_STORAGE_KEYS) { - const value = localStorage.getItem(key); - if (value) { - const features = value.toLowerCase().split(',').map(f => f.trim()); - if (features.includes('all')) { - return new Set(['all']); - } - return new Set(features.filter(f => f)); + const storageValue = localStorage.getItem(PRIMARY_STORAGE_KEY); + if (storageValue) { + const features = storageValue.toLowerCase().split(',').map(f => f.trim()); + if (features.includes('all')) { + return new Set(['all']); } + return new Set(features.filter(f => f)); } - // Check environment variables - for (const key of ENABLED_FEATURES_ENV_KEYS) { - const value = process.env[key]; - if (value) { - const features = value.toLowerCase().split(',').map(f => f.trim()); - if (features.includes('all')) { - return new Set(['all']); - } - return new Set(features.filter(f => f)); + // Check environment variable + const envValue = process.env[PRIMARY_ENV_KEY]; + if (envValue) { + const features = envValue.toLowerCase().split(',').map(f => f.trim()); + if (features.includes('all')) { + return new Set(['all']); } + return new Set(features.filter(f => f)); } // Default: all features enabled @@ -74,8 +55,8 @@ export function isPodcastOnlyDemoMode(): boolean { } /** - * Check if the app should skip onboarding entirely. - * Returns true in podcast-only demo mode or when not using all features. + * Check if the app should skip onboarding. + * Returns true in podcast-only mode. */ export function shouldSkipOnboarding(): boolean { const enabled = getEnabledFeatures();