misc improvements
This commit is contained in:
parent
2c8cbc95a5
commit
9ae40dda50
4 changed files with 334 additions and 43 deletions
|
|
@ -27,7 +27,7 @@ class OpenRAGTUI(App):
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
Screen {
|
Screen {
|
||||||
background: $background;
|
background: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-container {
|
#main-container {
|
||||||
|
|
@ -115,7 +115,8 @@ class OpenRAGTUI(App):
|
||||||
}
|
}
|
||||||
|
|
||||||
#services-table {
|
#services-table {
|
||||||
height: 1fr;
|
height: auto;
|
||||||
|
max-height: 12;
|
||||||
margin-bottom: 1;
|
margin-bottom: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +176,82 @@ class OpenRAGTUI(App):
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 1;
|
padding: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Frontend-inspired color scheme */
|
||||||
|
Static {
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.success {
|
||||||
|
background: #4ade80;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.error {
|
||||||
|
background: #ef4444;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.warning {
|
||||||
|
background: #eab308;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.primary {
|
||||||
|
background: #2563eb;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.default {
|
||||||
|
background: #475569;
|
||||||
|
color: #f1f5f9;
|
||||||
|
border: solid #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTable {
|
||||||
|
background: #1e293b;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTable > .datatable--header {
|
||||||
|
background: #334155;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTable > .datatable--cursor {
|
||||||
|
background: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input {
|
||||||
|
background: #334155;
|
||||||
|
color: #f1f5f9;
|
||||||
|
border: solid #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
Footer {
|
||||||
|
background: #334155;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#runtime-status {
|
||||||
|
background: #1e293b;
|
||||||
|
border: solid #64748b;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#system-info {
|
||||||
|
background: #1e293b;
|
||||||
|
border: solid #64748b;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#services-table, #images-table {
|
||||||
|
background: #1e293b;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
||||||
|
|
@ -273,8 +273,6 @@ class MonitorScreen(Screen):
|
||||||
self.run_worker(self._stop_docling_serve())
|
self.run_worker(self._stop_docling_serve())
|
||||||
elif button_id.startswith("docling-restart-btn"):
|
elif button_id.startswith("docling-restart-btn"):
|
||||||
self.run_worker(self._restart_docling_serve())
|
self.run_worker(self._restart_docling_serve())
|
||||||
elif button_id.startswith("docling-logs-btn"):
|
|
||||||
self._view_docling_logs()
|
|
||||||
elif button_id == "toggle-mode-btn":
|
elif button_id == "toggle-mode-btn":
|
||||||
self.action_toggle_mode()
|
self.action_toggle_mode()
|
||||||
elif button_id.startswith("refresh-btn"):
|
elif button_id.startswith("refresh-btn"):
|
||||||
|
|
@ -621,9 +619,6 @@ class MonitorScreen(Screen):
|
||||||
docling_controls.mount(
|
docling_controls.mount(
|
||||||
Button("Restart", variant="primary", id=f"docling-restart-btn{suffix}")
|
Button("Restart", variant="primary", id=f"docling-restart-btn{suffix}")
|
||||||
)
|
)
|
||||||
docling_controls.mount(
|
|
||||||
Button("View Logs", variant="default", id=f"docling-logs-btn{suffix}")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
docling_controls.mount(
|
docling_controls.mount(
|
||||||
Button("Start", variant="success", id=f"docling-start-btn{suffix}")
|
Button("Start", variant="success", id=f"docling-start-btn{suffix}")
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ from dotenv import load_dotenv
|
||||||
|
|
||||||
from ..managers.container_manager import ContainerManager, ServiceStatus
|
from ..managers.container_manager import ContainerManager, ServiceStatus
|
||||||
from ..managers.env_manager import EnvManager
|
from ..managers.env_manager import EnvManager
|
||||||
|
from ..managers.docling_manager import DoclingManager
|
||||||
|
from ..widgets.command_modal import CommandOutputModal
|
||||||
|
|
||||||
|
|
||||||
class WelcomeScreen(Screen):
|
class WelcomeScreen(Screen):
|
||||||
|
|
@ -22,15 +24,19 @@ class WelcomeScreen(Screen):
|
||||||
("enter", "default_action", "Continue"),
|
("enter", "default_action", "Continue"),
|
||||||
("1", "no_auth_setup", "Basic Setup"),
|
("1", "no_auth_setup", "Basic Setup"),
|
||||||
("2", "full_setup", "Advanced Setup"),
|
("2", "full_setup", "Advanced Setup"),
|
||||||
("3", "monitor", "Monitor Services"),
|
("3", "monitor", "Status"),
|
||||||
("4", "diagnostics", "Diagnostics"),
|
("4", "diagnostics", "Diagnostics"),
|
||||||
|
("5", "start_stop_services", "Start/Stop Services"),
|
||||||
|
("6", "open_app", "Open App"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.container_manager = ContainerManager()
|
self.container_manager = ContainerManager()
|
||||||
self.env_manager = EnvManager()
|
self.env_manager = EnvManager()
|
||||||
|
self.docling_manager = DoclingManager()
|
||||||
self.services_running = False
|
self.services_running = False
|
||||||
|
self.docling_running = False
|
||||||
self.has_oauth_config = False
|
self.has_oauth_config = False
|
||||||
self.default_button_id = "basic-setup-btn"
|
self.default_button_id = "basic-setup-btn"
|
||||||
self._state_checked = False
|
self._state_checked = False
|
||||||
|
|
@ -38,8 +44,16 @@ class WelcomeScreen(Screen):
|
||||||
# Load .env file if it exists
|
# Load .env file if it exists
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
# Check OAuth config immediately
|
||||||
|
self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool(
|
||||||
|
os.getenv("MICROSOFT_GRAPH_OAUTH_CLIENT_ID")
|
||||||
|
)
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
"""Create the welcome screen layout."""
|
"""Create the welcome screen layout."""
|
||||||
|
# Try to detect services synchronously before creating buttons
|
||||||
|
self._detect_services_sync()
|
||||||
|
|
||||||
yield Container(
|
yield Container(
|
||||||
Vertical(
|
Vertical(
|
||||||
Static(self._create_welcome_text(), id="welcome-text"),
|
Static(self._create_welcome_text(), id="welcome-text"),
|
||||||
|
|
@ -50,6 +64,46 @@ class WelcomeScreen(Screen):
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
def _detect_services_sync(self) -> None:
|
||||||
|
"""Synchronously detect if services are running."""
|
||||||
|
if not self.container_manager.is_available():
|
||||||
|
self.services_running = False
|
||||||
|
self.docling_running = self.docling_manager.is_running()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use synchronous docker command to check services
|
||||||
|
import subprocess
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "compose", "ps", "--format", "json"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
import json
|
||||||
|
services = []
|
||||||
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
try:
|
||||||
|
service = json.loads(line)
|
||||||
|
services.append(service)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if any services are running
|
||||||
|
running_services = [s for s in services if s.get('State') == 'running']
|
||||||
|
self.services_running = len(running_services) > 0
|
||||||
|
else:
|
||||||
|
self.services_running = False
|
||||||
|
except Exception:
|
||||||
|
# Fallback to False if detection fails
|
||||||
|
self.services_running = False
|
||||||
|
|
||||||
|
# Update native service state as part of detection
|
||||||
|
self.docling_running = self.docling_manager.is_running()
|
||||||
|
|
||||||
def _create_welcome_text(self) -> Text:
|
def _create_welcome_text(self) -> Text:
|
||||||
"""Create a minimal welcome message."""
|
"""Create a minimal welcome message."""
|
||||||
welcome_text = Text()
|
welcome_text = Text()
|
||||||
|
|
@ -61,7 +115,7 @@ class WelcomeScreen(Screen):
|
||||||
╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║╚██████╔╝
|
╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║╚██████╔╝
|
||||||
╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝
|
╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝
|
||||||
"""
|
"""
|
||||||
welcome_text.append(ascii_art, style="bold blue")
|
welcome_text.append(ascii_art, style="bold white")
|
||||||
welcome_text.append("Terminal User Interface for OpenRAG\n\n", style="dim")
|
welcome_text.append("Terminal User Interface for OpenRAG\n\n", style="dim")
|
||||||
|
|
||||||
if self.services_running:
|
if self.services_running:
|
||||||
|
|
@ -87,28 +141,56 @@ class WelcomeScreen(Screen):
|
||||||
buttons = []
|
buttons = []
|
||||||
|
|
||||||
if self.services_running:
|
if self.services_running:
|
||||||
# Services running - only show monitor
|
# Services running - show app link first, then stop services
|
||||||
buttons.append(
|
buttons.append(
|
||||||
Button("Monitor Services", variant="success", id="monitor-btn")
|
Button("Launch OpenRAG", variant="success", id="open-app-btn")
|
||||||
|
)
|
||||||
|
buttons.append(
|
||||||
|
Button("Stop Container Services", variant="error", id="stop-services-btn")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Services not running - show setup options
|
# Services not running - show setup options and start services
|
||||||
if has_oauth:
|
if has_oauth:
|
||||||
# Only show advanced setup if OAuth is configured
|
# If OAuth is configured, only show advanced setup
|
||||||
buttons.append(
|
buttons.append(
|
||||||
Button("Advanced Setup", variant="success", id="advanced-setup-btn")
|
Button("Advanced Setup", variant="success", id="advanced-setup-btn")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Only show basic setup if no OAuth
|
# If no OAuth, show both options with basic as primary
|
||||||
buttons.append(
|
buttons.append(
|
||||||
Button("Basic Setup", variant="success", id="basic-setup-btn")
|
Button("Basic Setup", variant="success", id="basic-setup-btn")
|
||||||
)
|
)
|
||||||
|
buttons.append(
|
||||||
|
Button("Advanced Setup", variant="default", id="advanced-setup-btn")
|
||||||
|
)
|
||||||
|
|
||||||
# Always show monitor option
|
|
||||||
buttons.append(
|
buttons.append(
|
||||||
Button("Monitor Services", variant="default", id="monitor-btn")
|
Button("Start Container Services", variant="primary", id="start-services-btn")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Native services controls
|
||||||
|
if self.docling_running:
|
||||||
|
buttons.append(
|
||||||
|
Button(
|
||||||
|
"Stop Native Services",
|
||||||
|
variant="warning",
|
||||||
|
id="stop-native-services-btn",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
buttons.append(
|
||||||
|
Button(
|
||||||
|
"Start Native Services",
|
||||||
|
variant="primary",
|
||||||
|
id="start-native-services-btn",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Always show status option
|
||||||
|
buttons.append(
|
||||||
|
Button("Status", variant="default", id="status-btn")
|
||||||
|
)
|
||||||
|
|
||||||
return Horizontal(*buttons, classes="button-row")
|
return Horizontal(*buttons, classes="button-row")
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
|
|
@ -121,6 +203,10 @@ class WelcomeScreen(Screen):
|
||||||
]
|
]
|
||||||
self.services_running = len(running_services) > 0
|
self.services_running = len(running_services) > 0
|
||||||
|
|
||||||
|
# Check native service state
|
||||||
|
self.docling_running = self.docling_manager.is_running()
|
||||||
|
|
||||||
|
|
||||||
# Check for OAuth configuration
|
# Check for OAuth configuration
|
||||||
self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool(
|
self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool(
|
||||||
os.getenv("MICROSOFT_GRAPH_OAUTH_CLIENT_ID")
|
os.getenv("MICROSOFT_GRAPH_OAUTH_CLIENT_ID")
|
||||||
|
|
@ -128,38 +214,34 @@ class WelcomeScreen(Screen):
|
||||||
|
|
||||||
# Set default button focus
|
# Set default button focus
|
||||||
if self.services_running:
|
if self.services_running:
|
||||||
self.default_button_id = "monitor-btn"
|
self.default_button_id = "open-app-btn"
|
||||||
elif self.has_oauth_config:
|
elif self.has_oauth_config:
|
||||||
self.default_button_id = "advanced-setup-btn"
|
self.default_button_id = "advanced-setup-btn"
|
||||||
else:
|
else:
|
||||||
self.default_button_id = "basic-setup-btn"
|
self.default_button_id = "basic-setup-btn"
|
||||||
|
|
||||||
# Update the welcome text and recompose with new state
|
# Update the welcome text
|
||||||
try:
|
try:
|
||||||
welcome_widget = self.query_one("#welcome-text")
|
welcome_widget = self.query_one("#welcome-text")
|
||||||
welcome_widget.update(
|
welcome_widget.update(self._create_welcome_text())
|
||||||
self._create_welcome_text()
|
|
||||||
) # This is fine for Static widgets
|
|
||||||
|
|
||||||
# Focus the appropriate button
|
|
||||||
if self.services_running:
|
|
||||||
try:
|
|
||||||
self.query_one("#monitor-btn").focus()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.has_oauth_config:
|
|
||||||
try:
|
|
||||||
self.query_one("#advanced-setup-btn").focus()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.query_one("#basic-setup-btn").focus()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
pass # Widgets might not be mounted yet
|
pass # Widget might not be mounted yet
|
||||||
|
|
||||||
|
# Focus the appropriate button (the buttons are created correctly in compose,
|
||||||
|
# the issue was they weren't being updated after service operations)
|
||||||
|
self.call_after_refresh(self._focus_appropriate_button)
|
||||||
|
|
||||||
|
def _focus_appropriate_button(self) -> None:
|
||||||
|
"""Focus the appropriate button based on current state."""
|
||||||
|
try:
|
||||||
|
if self.services_running:
|
||||||
|
self.query_one("#open-app-btn").focus()
|
||||||
|
elif self.has_oauth_config:
|
||||||
|
self.query_one("#advanced-setup-btn").focus()
|
||||||
|
else:
|
||||||
|
self.query_one("#basic-setup-btn").focus()
|
||||||
|
except:
|
||||||
|
pass # Button might not exist
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
"""Handle button presses."""
|
"""Handle button presses."""
|
||||||
|
|
@ -167,15 +249,25 @@ class WelcomeScreen(Screen):
|
||||||
self.action_no_auth_setup()
|
self.action_no_auth_setup()
|
||||||
elif event.button.id == "advanced-setup-btn":
|
elif event.button.id == "advanced-setup-btn":
|
||||||
self.action_full_setup()
|
self.action_full_setup()
|
||||||
elif event.button.id == "monitor-btn":
|
elif event.button.id == "status-btn":
|
||||||
self.action_monitor()
|
self.action_monitor()
|
||||||
elif event.button.id == "diagnostics-btn":
|
elif event.button.id == "diagnostics-btn":
|
||||||
self.action_diagnostics()
|
self.action_diagnostics()
|
||||||
|
elif event.button.id == "start-services-btn":
|
||||||
|
self.action_start_stop_services()
|
||||||
|
elif event.button.id == "stop-services-btn":
|
||||||
|
self.action_start_stop_services()
|
||||||
|
elif event.button.id == "start-native-services-btn":
|
||||||
|
self.action_start_native_services()
|
||||||
|
elif event.button.id == "stop-native-services-btn":
|
||||||
|
self.action_stop_native_services()
|
||||||
|
elif event.button.id == "open-app-btn":
|
||||||
|
self.action_open_app()
|
||||||
|
|
||||||
def action_default_action(self) -> None:
|
def action_default_action(self) -> None:
|
||||||
"""Handle Enter key - go to default action based on state."""
|
"""Handle Enter key - go to default action based on state."""
|
||||||
if self.services_running:
|
if self.services_running:
|
||||||
self.action_monitor()
|
self.action_open_app()
|
||||||
elif self.has_oauth_config:
|
elif self.has_oauth_config:
|
||||||
self.action_full_setup()
|
self.action_full_setup()
|
||||||
else:
|
else:
|
||||||
|
|
@ -205,6 +297,126 @@ class WelcomeScreen(Screen):
|
||||||
|
|
||||||
self.app.push_screen(DiagnosticsScreen())
|
self.app.push_screen(DiagnosticsScreen())
|
||||||
|
|
||||||
|
def action_start_stop_services(self) -> None:
|
||||||
|
"""Start or stop all services (containers + docling)."""
|
||||||
|
if self.services_running:
|
||||||
|
# Stop services - show modal with progress
|
||||||
|
if self.container_manager.is_available():
|
||||||
|
command_generator = self.container_manager.stop_services()
|
||||||
|
modal = CommandOutputModal(
|
||||||
|
"Stopping Services",
|
||||||
|
command_generator,
|
||||||
|
on_complete=self._on_services_operation_complete,
|
||||||
|
)
|
||||||
|
self.app.push_screen(modal)
|
||||||
|
else:
|
||||||
|
# Start services - show modal with progress
|
||||||
|
if self.container_manager.is_available():
|
||||||
|
command_generator = self.container_manager.start_services()
|
||||||
|
modal = CommandOutputModal(
|
||||||
|
"Starting Services",
|
||||||
|
command_generator,
|
||||||
|
on_complete=self._on_services_operation_complete,
|
||||||
|
)
|
||||||
|
self.app.push_screen(modal)
|
||||||
|
|
||||||
|
async def _on_services_operation_complete(self) -> None:
|
||||||
|
"""Handle completion of services start/stop operation."""
|
||||||
|
# Use the same sync detection method that worked on startup
|
||||||
|
self._detect_services_sync()
|
||||||
|
|
||||||
|
# Update OAuth config state
|
||||||
|
self.has_oauth_config = bool(os.getenv("GOOGLE_OAUTH_CLIENT_ID")) or bool(
|
||||||
|
os.getenv("MICROSOFT_GRAPH_OAUTH_CLIENT_ID")
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._refresh_welcome_content()
|
||||||
|
|
||||||
|
def _update_default_button(self) -> None:
|
||||||
|
"""Update the default button target based on state."""
|
||||||
|
if self.services_running:
|
||||||
|
self.default_button_id = "open-app-btn"
|
||||||
|
elif self.has_oauth_config:
|
||||||
|
self.default_button_id = "advanced-setup-btn"
|
||||||
|
else:
|
||||||
|
self.default_button_id = "basic-setup-btn"
|
||||||
|
|
||||||
|
async def _refresh_welcome_content(self) -> None:
|
||||||
|
"""Refresh welcome text and buttons based on current state."""
|
||||||
|
self._update_default_button()
|
||||||
|
|
||||||
|
try:
|
||||||
|
welcome_widget = self.query_one("#welcome-text", Static)
|
||||||
|
welcome_widget.update(self._create_welcome_text())
|
||||||
|
|
||||||
|
welcome_container = self.query_one("#welcome-container")
|
||||||
|
|
||||||
|
# Remove existing button rows before mounting updated row
|
||||||
|
for button_row in list(welcome_container.query(".button-row")):
|
||||||
|
await button_row.remove()
|
||||||
|
|
||||||
|
await welcome_container.mount(self._create_dynamic_buttons())
|
||||||
|
except Exception:
|
||||||
|
# Fallback - just refresh the whole screen
|
||||||
|
self.refresh(layout=True)
|
||||||
|
|
||||||
|
self.call_after_refresh(self._focus_appropriate_button)
|
||||||
|
|
||||||
|
def action_start_native_services(self) -> None:
|
||||||
|
"""Start native services (docling)."""
|
||||||
|
if self.docling_running:
|
||||||
|
self.notify("Native services are already running.", severity="warning")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.run_worker(self._start_native_services())
|
||||||
|
|
||||||
|
async def _start_native_services(self) -> None:
|
||||||
|
"""Worker task to start native services."""
|
||||||
|
try:
|
||||||
|
success, message = await self.docling_manager.start()
|
||||||
|
if success:
|
||||||
|
self.docling_running = True
|
||||||
|
self.notify(message, severity="information")
|
||||||
|
else:
|
||||||
|
self.notify(f"Failed to start native services: {message}", severity="error")
|
||||||
|
except Exception as exc:
|
||||||
|
self.notify(f"Error starting native services: {exc}", severity="error")
|
||||||
|
finally:
|
||||||
|
self.docling_running = self.docling_manager.is_running()
|
||||||
|
await self._refresh_welcome_content()
|
||||||
|
|
||||||
|
def action_stop_native_services(self) -> None:
|
||||||
|
"""Stop native services (docling)."""
|
||||||
|
if not self.docling_running and not self.docling_manager.is_running():
|
||||||
|
self.notify("Native services are not running.", severity="warning")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.run_worker(self._stop_native_services())
|
||||||
|
|
||||||
|
async def _stop_native_services(self) -> None:
|
||||||
|
"""Worker task to stop native services."""
|
||||||
|
try:
|
||||||
|
success, message = await self.docling_manager.stop()
|
||||||
|
if success:
|
||||||
|
self.docling_running = False
|
||||||
|
self.notify(message, severity="information")
|
||||||
|
else:
|
||||||
|
self.notify(f"Failed to stop native services: {message}", severity="error")
|
||||||
|
except Exception as exc:
|
||||||
|
self.notify(f"Error stopping native services: {exc}", severity="error")
|
||||||
|
finally:
|
||||||
|
self.docling_running = self.docling_manager.is_running()
|
||||||
|
await self._refresh_welcome_content()
|
||||||
|
|
||||||
|
def action_open_app(self) -> None:
|
||||||
|
"""Open the OpenRAG app in the default browser."""
|
||||||
|
import webbrowser
|
||||||
|
try:
|
||||||
|
webbrowser.open("http://localhost:3000")
|
||||||
|
self.notify("Opening OpenRAG app in browser...", severity="information")
|
||||||
|
except Exception as e:
|
||||||
|
self.notify(f"Error opening app: {e}", severity="error")
|
||||||
|
|
||||||
def action_quit(self) -> None:
|
def action_quit(self) -> None:
|
||||||
"""Quit the application."""
|
"""Quit the application."""
|
||||||
self.app.exit()
|
self.app.exit()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Command output modal dialog for OpenRAG TUI."""
|
"""Command output modal dialog for OpenRAG TUI."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import inspect
|
||||||
from typing import Callable, List, Optional, AsyncIterator, Any
|
from typing import Callable, List, Optional, AsyncIterator, Any
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
|
|
@ -122,7 +123,13 @@ class CommandOutputModal(ModalScreen):
|
||||||
# Call the completion callback if provided
|
# Call the completion callback if provided
|
||||||
if self.on_complete:
|
if self.on_complete:
|
||||||
await asyncio.sleep(0.5) # Small delay for better UX
|
await asyncio.sleep(0.5) # Small delay for better UX
|
||||||
self.on_complete()
|
|
||||||
|
def _invoke_callback() -> None:
|
||||||
|
callback_result = self.on_complete()
|
||||||
|
if inspect.isawaitable(callback_result):
|
||||||
|
asyncio.create_task(callback_result)
|
||||||
|
|
||||||
|
self.call_after_refresh(_invoke_callback)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
output.write(f"[bold red]Error: {e}[/bold red]\n")
|
output.write(f"[bold red]Error: {e}[/bold red]\n")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue