Merge branch 'main' into docs-20-nov-25
This commit is contained in:
commit
6dc8ba233a
11 changed files with 385 additions and 85 deletions
|
|
@ -391,7 +391,7 @@ function SearchPage() {
|
|||
<DeleteConfirmationDialog
|
||||
open={showBulkDeleteDialog}
|
||||
onOpenChange={setShowBulkDeleteDialog}
|
||||
title="Delete Documents"
|
||||
title={selectedRows.length > 1 ? "Delete Documents" : "Delete Document"}
|
||||
description={`Are you sure you want to delete ${
|
||||
selectedRows.length
|
||||
} document${
|
||||
|
|
@ -400,7 +400,7 @@ function SearchPage() {
|
|||
|
||||
Documents to be deleted:
|
||||
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
||||
confirmText="Delete All"
|
||||
confirmText={selectedRows.length > 1 ? "Delete All" : "Delete"}
|
||||
onConfirm={handleBulkDelete}
|
||||
isLoading={deleteDocumentMutation.isPending}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export function OnboardingContent({
|
|||
/>
|
||||
</OnboardingStep>
|
||||
|
||||
{/* Step 2 */}
|
||||
{/* Step 3 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 2}
|
||||
isCompleted={currentStep > 2 || !!selectedNudge}
|
||||
|
|
@ -209,7 +209,7 @@ export function OnboardingContent({
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* Step 3 */}
|
||||
{/* Step 4 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 3 && !isLoading && !!displayMessage}
|
||||
isCompleted={currentStep > 3}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,33 @@ import {
|
|||
} from "@/lib/upload-utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Supported file extensions - single source of truth
|
||||
const SUPPORTED_EXTENSIONS = [
|
||||
".pdf",
|
||||
".doc",
|
||||
".docx",
|
||||
".pptx",
|
||||
".ppt",
|
||||
".xlsx",
|
||||
".xls",
|
||||
".csv",
|
||||
".txt",
|
||||
".md",
|
||||
".html",
|
||||
".htm",
|
||||
".rtf",
|
||||
".odt",
|
||||
".asciidoc",
|
||||
".adoc",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".bmp",
|
||||
".tiff",
|
||||
".webp",
|
||||
];
|
||||
|
||||
export function KnowledgeDropdown() {
|
||||
const { addTask } = useTask();
|
||||
const { refetch: refetchTasks } = useGetTasksQuery();
|
||||
|
|
@ -247,27 +274,18 @@ export function KnowledgeDropdown() {
|
|||
|
||||
try {
|
||||
const fileList = Array.from(files);
|
||||
const supportedExtensions = [
|
||||
".pdf",
|
||||
".doc",
|
||||
".docx",
|
||||
".txt",
|
||||
".md",
|
||||
".rtf",
|
||||
".odt",
|
||||
];
|
||||
|
||||
const filteredFiles = fileList.filter((file) => {
|
||||
const ext = file.name
|
||||
.substring(file.name.lastIndexOf("."))
|
||||
.toLowerCase();
|
||||
return supportedExtensions.includes(ext);
|
||||
return SUPPORTED_EXTENSIONS.includes(ext);
|
||||
});
|
||||
|
||||
if (filteredFiles.length === 0) {
|
||||
toast.error("No supported files found", {
|
||||
description:
|
||||
"Please select a folder containing PDF, DOC, DOCX, TXT, MD, RTF, or ODT files.",
|
||||
"Please select a folder containing supported document files (PDF, DOCX, PPTX, XLSX, CSV, HTML, images, etc.).",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -527,7 +545,7 @@ export function KnowledgeDropdown() {
|
|||
type="file"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
||||
accept={SUPPORTED_EXTENSIONS.join(",")}
|
||||
/>
|
||||
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const UI_CONSTANTS = {
|
|||
export const ANIMATION_DURATION = 0.4;
|
||||
export const SIDEBAR_WIDTH = 280;
|
||||
export const HEADER_HEIGHT = 54;
|
||||
export const TOTAL_ONBOARDING_STEPS = 5;
|
||||
export const TOTAL_ONBOARDING_STEPS = 4;
|
||||
|
||||
/**
|
||||
* Local Storage Keys
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ class AppClients:
|
|||
|
||||
# Initialize Langflow HTTP client
|
||||
self.langflow_http_client = httpx.AsyncClient(
|
||||
base_url=LANGFLOW_URL, timeout=300.0
|
||||
base_url=LANGFLOW_URL, timeout=1200.0
|
||||
)
|
||||
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
@ -109,57 +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",
|
||||
"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."""
|
||||
|
|
@ -197,6 +222,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 +338,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 +346,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")
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
@ -100,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()
|
||||
|
|
@ -111,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:
|
||||
|
|
@ -203,14 +254,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 +277,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 +701,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."""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
4
uv.lock
generated
4
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
resolution-markers = [
|
||||
"platform_machine == 'x86_64' and sys_platform == 'linux'",
|
||||
|
|
@ -2352,7 +2352,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "openrag"
|
||||
version = "0.1.35"
|
||||
version = "0.1.37"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "agentd" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue