From 9e4502d07b87c188383084eb4d080c61614db60d Mon Sep 17 00:00:00 2001 From: phact Date: Fri, 21 Nov 2025 16:59:26 -0500 Subject: [PATCH 1/3] pick up env vars for config (not just existing .env files) --- src/tui/managers/env_manager.py | 115 ++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/src/tui/managers/env_manager.py b/src/tui/managers/env_manager.py index 8bfb8369..b1fd73bb 100644 --- a/src/tui/managers/env_manager.py +++ b/src/tui/managers/env_manager.py @@ -116,62 +116,75 @@ class EnvManager: return f"'{escaped_value}'" def load_existing_env(self) -> bool: - """Load existing .env file if it exists.""" - if not self.env_file.exists(): - return False + """Load existing .env file if it exists, or fall back to environment variables.""" + import os + + # Map env vars to config attributes + attr_map = { + "OPENAI_API_KEY": "openai_api_key", + "ANTHROPIC_API_KEY": "anthropic_api_key", + "OLLAMA_ENDPOINT": "ollama_endpoint", + "WATSONX_API_KEY": "watsonx_api_key", + "WATSONX_ENDPOINT": "watsonx_endpoint", + "WATSONX_PROJECT_ID": "watsonx_project_id", + "OPENSEARCH_PASSWORD": "opensearch_password", + "LANGFLOW_SECRET_KEY": "langflow_secret_key", + "LANGFLOW_SUPERUSER": "langflow_superuser", + "LANGFLOW_SUPERUSER_PASSWORD": "langflow_superuser_password", + "LANGFLOW_CHAT_FLOW_ID": "langflow_chat_flow_id", + "LANGFLOW_INGEST_FLOW_ID": "langflow_ingest_flow_id", + "LANGFLOW_URL_INGEST_FLOW_ID": "langflow_url_ingest_flow_id", + "NUDGES_FLOW_ID": "nudges_flow_id", + "GOOGLE_OAUTH_CLIENT_ID": "google_oauth_client_id", + "GOOGLE_OAUTH_CLIENT_SECRET": "google_oauth_client_secret", + "MICROSOFT_GRAPH_OAUTH_CLIENT_ID": "microsoft_graph_oauth_client_id", + "MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET": "microsoft_graph_oauth_client_secret", + "WEBHOOK_BASE_URL": "webhook_base_url", + "AWS_ACCESS_KEY_ID": "aws_access_key_id", + "AWS_SECRET_ACCESS_KEY": "aws_secret_access_key", + "LANGFLOW_PUBLIC_URL": "langflow_public_url", + "OPENRAG_DOCUMENTS_PATHS": "openrag_documents_paths", + "OPENSEARCH_DATA_PATH": "opensearch_data_path", + "LANGFLOW_AUTO_LOGIN": "langflow_auto_login", + "LANGFLOW_NEW_USER_IS_ACTIVE": "langflow_new_user_is_active", + "LANGFLOW_ENABLE_SUPERUSER_CLI": "langflow_enable_superuser_cli", + "DISABLE_INGEST_WITH_LANGFLOW": "disable_ingest_with_langflow", + } + + loaded_from_file = False + + # Try to load from .env file first + if self.env_file.exists(): + try: + with open(self.env_file, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue - try: - with open(self.env_file, "r") as f: - for line in f: - line = line.strip() - if not line or line.startswith("#"): - continue + if "=" in line: + key, value = line.split("=", 1) + key = key.strip() + value = sanitize_env_value(value) - if "=" in line: - key, value = line.split("=", 1) - key = key.strip() - value = sanitize_env_value(value) + if key in attr_map: + setattr(self.config, attr_map[key], value) - # Map env vars to config attributes - attr_map = { - "OPENAI_API_KEY": "openai_api_key", - "ANTHROPIC_API_KEY": "anthropic_api_key", - "OLLAMA_ENDPOINT": "ollama_endpoint", - "WATSONX_API_KEY": "watsonx_api_key", - "WATSONX_ENDPOINT": "watsonx_endpoint", - "WATSONX_PROJECT_ID": "watsonx_project_id", - "OPENSEARCH_PASSWORD": "opensearch_password", - "LANGFLOW_SECRET_KEY": "langflow_secret_key", - "LANGFLOW_SUPERUSER": "langflow_superuser", - "LANGFLOW_SUPERUSER_PASSWORD": "langflow_superuser_password", - "LANGFLOW_CHAT_FLOW_ID": "langflow_chat_flow_id", - "LANGFLOW_INGEST_FLOW_ID": "langflow_ingest_flow_id", - "LANGFLOW_URL_INGEST_FLOW_ID": "langflow_url_ingest_flow_id", - "NUDGES_FLOW_ID": "nudges_flow_id", - "GOOGLE_OAUTH_CLIENT_ID": "google_oauth_client_id", - "GOOGLE_OAUTH_CLIENT_SECRET": "google_oauth_client_secret", - "MICROSOFT_GRAPH_OAUTH_CLIENT_ID": "microsoft_graph_oauth_client_id", - "MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET": "microsoft_graph_oauth_client_secret", - "WEBHOOK_BASE_URL": "webhook_base_url", - "AWS_ACCESS_KEY_ID": "aws_access_key_id", - "AWS_SECRET_ACCESS_KEY": "aws_secret_access_key", - "LANGFLOW_PUBLIC_URL": "langflow_public_url", - "OPENRAG_DOCUMENTS_PATHS": "openrag_documents_paths", - "OPENSEARCH_DATA_PATH": "opensearch_data_path", - "LANGFLOW_AUTO_LOGIN": "langflow_auto_login", - "LANGFLOW_NEW_USER_IS_ACTIVE": "langflow_new_user_is_active", - "LANGFLOW_ENABLE_SUPERUSER_CLI": "langflow_enable_superuser_cli", - "DISABLE_INGEST_WITH_LANGFLOW": "disable_ingest_with_langflow", - } - - if key in attr_map: - setattr(self.config, attr_map[key], value) + loaded_from_file = True + except Exception as e: + logger.error("Error loading .env file", error=str(e)) + + # Fall back to environment variables if .env file doesn't exist or failed to load + if not loaded_from_file: + logger.info("No .env file found, loading from environment variables") + for env_key, attr_name in attr_map.items(): + value = os.environ.get(env_key, "") + if value: + setattr(self.config, attr_name, value) return True - - except Exception as e: - logger.error("Error loading .env file", error=str(e)) - return False + + return loaded_from_file def setup_secure_defaults(self) -> None: """Set up secure default values for passwords and keys.""" From 406b783a1aafb28378a8c266229b0b43772460d0 Mon Sep 17 00:00:00 2001 From: phact Date: Fri, 21 Nov 2025 17:14:05 -0500 Subject: [PATCH 2/3] conditional buttons and copy --- src/tui/screens/config.py | 14 ++++++++++---- src/tui/screens/welcome.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/tui/screens/config.py b/src/tui/screens/config.py index df8d6e98..51662964 100644 --- a/src/tui/screens/config.py +++ b/src/tui/screens/config.py @@ -145,6 +145,9 @@ class ConfigScreen(Screen): self.mode = mode # "no_auth" or "full" self.env_manager = EnvManager() self.inputs = {} + + # Check if .env file exists + self.has_env_file = self.env_manager.env_file.exists() # Load existing config if available self.env_manager.load_existing_env() @@ -156,12 +159,15 @@ class ConfigScreen(Screen): with ScrollableContainer(id="config-scroll"): with Vertical(id="config-form"): yield from self._create_all_fields() - yield Horizontal( + # Create button row - conditionally include Back button + buttons = [ Button("Generate Passwords", variant="default", id="generate-btn"), Button("Save Configuration", variant="success", id="save-btn"), - Button("Back", variant="default", id="back-btn"), - classes="button-row", - ) + ] + # Only show Back button if .env file exists + if self.has_env_file: + buttons.append(Button("Back", variant="default", id="back-btn")) + yield Horizontal(*buttons, classes="button-row") yield Footer() def _create_header_text(self) -> Text: diff --git a/src/tui/screens/welcome.py b/src/tui/screens/welcome.py index 66321010..64ad888a 100644 --- a/src/tui/screens/welcome.py +++ b/src/tui/screens/welcome.py @@ -22,13 +22,6 @@ class WelcomeScreen(Screen): BINDINGS = [ ("q", "quit", "Quit"), - ("enter", "default_action", "Continue"), - ("1", "no_auth_setup", "Basic Setup"), - ("2", "full_setup", "Advanced Setup"), - ("3", "monitor", "Status"), - ("4", "diagnostics", "Diagnostics"), - ("5", "start_stop_services", "Start/Stop Services"), - ("6", "open_app", "Open App"), ] def __init__(self): @@ -41,6 +34,9 @@ class WelcomeScreen(Screen): self.has_oauth_config = False self.default_button_id = "basic-setup-btn" self._state_checked = False + + # Check if .env file exists + self.has_env_file = self.env_manager.env_file.exists() # Load .env file if it exists load_dotenv() @@ -161,6 +157,23 @@ class WelcomeScreen(Screen): buttons = [] + # If no .env file exists, only show setup buttons + if not self.has_env_file: + if has_oauth: + # If OAuth is configured, only show advanced setup + buttons.append( + Button("Advanced Setup", variant="success", id="advanced-setup-btn") + ) + else: + # If no OAuth, show both options with basic as primary + buttons.append( + Button("Basic Setup", variant="success", id="basic-setup-btn") + ) + buttons.append( + Button("Advanced Setup", variant="default", id="advanced-setup-btn") + ) + return Horizontal(*buttons, classes="button-row") + # Check if all services (native + container) are running all_services_running = self.services_running and self.docling_running @@ -189,7 +202,7 @@ class WelcomeScreen(Screen): ) buttons.append( - Button("Start All Services", variant="primary", id="start-all-services-btn") + Button("Start OpenRAG", variant="primary", id="start-all-services-btn") ) # Always show status option @@ -253,6 +266,9 @@ class WelcomeScreen(Screen): async def on_screen_resume(self) -> None: """Called when returning from another screen (e.g., config screen).""" + # Check if .env file exists (may have been created) + self.has_env_file = self.env_manager.env_file.exists() + # Reload environment variables load_dotenv(override=True) From 2322c0e14f0a687768422a4d6fabfef1f040ae7e Mon Sep 17 00:00:00 2001 From: phact Date: Fri, 21 Nov 2025 17:25:40 -0500 Subject: [PATCH 3/3] longer notifications --- src/tui/main.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tui/main.py b/src/tui/main.py index e0f60df2..19468473 100644 --- a/src/tui/main.py +++ b/src/tui/main.py @@ -365,6 +365,21 @@ class OpenRAGTUI(App): self.container_manager = ContainerManager() self.env_manager = EnvManager() self.docling_manager = DoclingManager() # Initialize singleton instance + + def notify( + self, + message: str, + *, + title: str = "", + severity: str = "information", + timeout: float | None = None, + markup: bool = True, + ) -> None: + """Override notify to make notifications last 20 seconds by default.""" + # If timeout is None (default), make it 20 seconds + if timeout is None: + timeout = 20.0 + super().notify(message, title=title, severity=severity, timeout=timeout, markup=markup) def on_mount(self) -> None: """Initialize the application."""