Merge branch 'main' into delete-dialog-copy

This commit is contained in:
Mike Fortman 2025-11-21 15:53:09 -06:00 committed by GitHub
commit b9d94f7149
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 239 additions and 7 deletions

View file

@ -35,6 +35,13 @@ class EnvConfig:
langflow_ingest_flow_id: str = "5488df7c-b93f-4f87-a446-b67028bc0813"
langflow_url_ingest_flow_id: str = "72c3d17c-2dac-4a73-b48a-6518473d7830"
# Provider API keys and endpoints
anthropic_api_key: str = ""
ollama_endpoint: str = ""
watsonx_api_key: str = ""
watsonx_endpoint: str = ""
watsonx_project_id: str = ""
# OAuth settings
google_oauth_client_id: str = ""
google_oauth_client_secret: str = ""
@ -128,6 +135,11 @@ class EnvManager:
# 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",
@ -197,6 +209,30 @@ class EnvManager:
"Invalid OpenAI API key format (should start with sk-)"
)
# Import validation functions for new provider fields
from ..utils.validation import validate_anthropic_api_key
# Validate Anthropic API key format if provided
if self.config.anthropic_api_key:
if not validate_anthropic_api_key(self.config.anthropic_api_key):
self.config.validation_errors["anthropic_api_key"] = (
"Invalid Anthropic API key format (should start with sk-ant-)"
)
# Validate Ollama endpoint if provided
if self.config.ollama_endpoint:
if not validate_url(self.config.ollama_endpoint):
self.config.validation_errors["ollama_endpoint"] = (
"Invalid Ollama endpoint URL format"
)
# Validate IBM watsonx.ai endpoint if provided
if self.config.watsonx_endpoint:
if not validate_url(self.config.watsonx_endpoint):
self.config.validation_errors["watsonx_endpoint"] = (
"Invalid IBM watsonx.ai endpoint URL format"
)
# Validate documents paths only if provided (optional)
if self.config.openrag_documents_paths:
is_valid, error_msg, _ = validate_documents_paths(
@ -289,9 +325,6 @@ class EnvManager:
f.write(f"LANGFLOW_URL_INGEST_FLOW_ID={self._quote_env_value(self.config.langflow_url_ingest_flow_id)}\n")
f.write(f"NUDGES_FLOW_ID={self._quote_env_value(self.config.nudges_flow_id)}\n")
f.write(f"OPENSEARCH_PASSWORD={self._quote_env_value(self.config.opensearch_password)}\n")
# Only write OpenAI API key if provided (can be set during onboarding instead)
if self.config.openai_api_key:
f.write(f"OPENAI_API_KEY={self._quote_env_value(self.config.openai_api_key)}\n")
f.write(
f"OPENRAG_DOCUMENTS_PATHS={self._quote_env_value(self.config.openrag_documents_paths)}\n"
)
@ -300,6 +333,27 @@ class EnvManager:
)
f.write("\n")
# Provider API keys and endpoints (optional - can be set during onboarding)
provider_vars = []
if self.config.openai_api_key:
provider_vars.append(("OPENAI_API_KEY", self.config.openai_api_key))
if self.config.anthropic_api_key:
provider_vars.append(("ANTHROPIC_API_KEY", self.config.anthropic_api_key))
if self.config.ollama_endpoint:
provider_vars.append(("OLLAMA_ENDPOINT", self.config.ollama_endpoint))
if self.config.watsonx_api_key:
provider_vars.append(("WATSONX_API_KEY", self.config.watsonx_api_key))
if self.config.watsonx_endpoint:
provider_vars.append(("WATSONX_ENDPOINT", self.config.watsonx_endpoint))
if self.config.watsonx_project_id:
provider_vars.append(("WATSONX_PROJECT_ID", self.config.watsonx_project_id))
if provider_vars:
f.write("# AI Provider API Keys and Endpoints\n")
for var_name, var_value in provider_vars:
f.write(f"{var_name}={self._quote_env_value(var_value)}\n")
f.write("\n")
# Ingestion settings
f.write("# Ingestion settings\n")
f.write(f"DISABLE_INGEST_WITH_LANGFLOW={self._quote_env_value(self.config.disable_ingest_with_langflow)}\n")

View file

@ -20,7 +20,13 @@ from rich.text import Text
from pathlib import Path
from ..managers.env_manager import EnvManager
from ..utils.validation import validate_openai_api_key, validate_documents_paths
from ..utils.validation import (
validate_openai_api_key,
validate_anthropic_api_key,
validate_ollama_endpoint,
validate_watsonx_endpoint,
validate_documents_paths,
)
from pathlib import Path
@ -37,6 +43,45 @@ class OpenAIKeyValidator(Validator):
return self.failure("Invalid OpenAI API key format (should start with sk-)")
class AnthropicKeyValidator(Validator):
"""Validator for Anthropic API keys."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_anthropic_api_key(value):
return self.success()
else:
return self.failure("Invalid Anthropic API key format (should start with sk-ant-)")
class OllamaEndpointValidator(Validator):
"""Validator for Ollama endpoint URLs."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_ollama_endpoint(value):
return self.success()
else:
return self.failure("Invalid Ollama endpoint URL format")
class WatsonxEndpointValidator(Validator):
"""Validator for IBM watsonx.ai endpoint URLs."""
def validate(self, value: str) -> ValidationResult:
if not value:
return self.success()
if validate_watsonx_endpoint(value):
return self.success()
else:
return self.failure("Invalid watsonx.ai endpoint URL format")
class DocumentsPathValidator(Validator):
"""Validator for documents paths."""
@ -203,14 +248,13 @@ class ConfigScreen(Screen):
yield Static(" ")
# OpenAI API Key
yield Label("OpenAI API Key")
# Where to create OpenAI keys (helper above the box)
yield Label("OpenAI API Key (optional)")
yield Static(
Text("Get a key: https://platform.openai.com/api-keys", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can also be provided during onboarding", style="dim italic"),
Text("Can be configured later in the UI", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "openai_api_key", "")
@ -227,6 +271,107 @@ class ConfigScreen(Screen):
yield Button("Show", id="toggle-openai-key", variant="default")
yield Static(" ")
# Anthropic API Key
yield Label("Anthropic API Key (optional)")
yield Static(
Text("Get a key: https://console.anthropic.com/settings/keys", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can be configured later in the UI", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "anthropic_api_key", "")
with Horizontal(id="anthropic-key-row"):
input_widget = Input(
placeholder="sk-ant-...",
value=current_value,
password=True,
validators=[AnthropicKeyValidator()],
id="input-anthropic_api_key",
)
yield input_widget
self.inputs["anthropic_api_key"] = input_widget
yield Button("Show", id="toggle-anthropic-key", variant="default")
yield Static(" ")
# Ollama Endpoint
yield Label("Ollama Base URL (optional)")
yield Static(
Text("Endpoint of your Ollama server", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can be configured later in the UI", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "ollama_endpoint", "")
input_widget = Input(
placeholder="http://localhost:11434",
value=current_value,
validators=[OllamaEndpointValidator()],
id="input-ollama_endpoint",
)
yield input_widget
self.inputs["ollama_endpoint"] = input_widget
yield Static(" ")
# IBM watsonx.ai API Key
yield Label("IBM watsonx.ai API Key (optional)")
yield Static(
Text("Get a key: https://cloud.ibm.com/iam/apikeys", style="dim"),
classes="helper-text",
)
yield Static(
Text("Can be configured later in the UI", style="dim italic"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_api_key", "")
with Horizontal(id="watsonx-key-row"):
input_widget = Input(
placeholder="",
value=current_value,
password=True,
id="input-watsonx_api_key",
)
yield input_widget
self.inputs["watsonx_api_key"] = input_widget
yield Button("Show", id="toggle-watsonx-key", variant="default")
yield Static(" ")
# IBM watsonx.ai Endpoint
yield Label("IBM watsonx.ai Endpoint")
yield Static(
Text("Example: https://us-south.ml.cloud.ibm.com", style="dim"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_endpoint", "")
input_widget = Input(
placeholder="https://us-south.ml.cloud.ibm.com",
value=current_value,
validators=[WatsonxEndpointValidator()],
id="input-watsonx_endpoint",
)
yield input_widget
self.inputs["watsonx_endpoint"] = input_widget
yield Static(" ")
# IBM watsonx.ai Project ID
yield Label("IBM watsonx.ai Project ID")
yield Static(
Text("Find in your IBM Cloud project settings", style="dim"),
classes="helper-text",
)
current_value = getattr(self.env_manager.config, "watsonx_project_id", "")
input_widget = Input(
placeholder="",
value=current_value,
id="input-watsonx_project_id",
)
yield input_widget
self.inputs["watsonx_project_id"] = input_widget
yield Static(" ")
# Add OAuth fields only in full mode
if self.mode == "full":
# Google OAuth Client ID
@ -550,6 +695,18 @@ class ConfigScreen(Screen):
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "🙈" if not input_widget.password else "👁"
elif event.button.id == "toggle-anthropic-key":
# Toggle Anthropic API key visibility
input_widget = self.inputs.get("anthropic_api_key")
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "Hide" if not input_widget.password else "Show"
elif event.button.id == "toggle-watsonx-key":
# Toggle watsonx API key visibility
input_widget = self.inputs.get("watsonx_api_key")
if input_widget:
input_widget.password = not input_widget.password
event.button.label = "Hide" if not input_widget.password else "Show"
def action_generate(self) -> None:
"""Generate secure passwords for admin accounts."""

View file

@ -63,6 +63,27 @@ def validate_openai_api_key(key: str) -> bool:
return key.startswith("sk-") and len(key) > 20
def validate_anthropic_api_key(key: str) -> bool:
"""Validate Anthropic API key format."""
if not key:
return False
return key.startswith("sk-ant-") and len(key) > 20
def validate_ollama_endpoint(endpoint: str) -> bool:
"""Validate Ollama endpoint URL format."""
if not endpoint:
return False
return validate_url(endpoint)
def validate_watsonx_endpoint(endpoint: str) -> bool:
"""Validate IBM watsonx.ai endpoint URL format."""
if not endpoint:
return False
return validate_url(endpoint)
def validate_google_oauth_client_id(client_id: str) -> bool:
"""Validate Google OAuth client ID format."""
if not client_id: