From 3a6a05d0437a88084a58fe476c65930eabd923fc Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:25:22 -0300 Subject: [PATCH 1/2] Fix: reduce docling and provider banner refresh interval, implemented Starting on docling TUI (#404) * Fixed refetch interval to be 3 seconds when Docling is unhealthy, fixed query to refetch on window focus * Changed time to refetch provider health * Added starting state to Docling on the TUI --- .../app/api/queries/useDoclingHealthQuery.ts | 9 +++- .../app/api/queries/useProviderHealthQuery.ts | 7 +++ src/tui/managers/docling_manager.py | 29 ++++++++++++ src/tui/screens/monitor.py | 44 ++++++++++++++++--- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/api/queries/useDoclingHealthQuery.ts b/frontend/src/app/api/queries/useDoclingHealthQuery.ts index 88c0a39b..01441f4b 100644 --- a/frontend/src/app/api/queries/useDoclingHealthQuery.ts +++ b/frontend/src/app/api/queries/useDoclingHealthQuery.ts @@ -56,8 +56,13 @@ export const useDoclingHealthQuery = ( queryKey: ["docling-health"], queryFn: checkDoclingHealth, retry: 1, - refetchInterval: 30000, // Check every 30 seconds - staleTime: 25000, // Consider data stale after 25 seconds + refetchInterval: (query) => { + // If healthy, check every 30 seconds; otherwise check every 3 seconds + return query.state.data?.status === "healthy" ? 30000 : 3000; + }, + refetchOnWindowFocus: true, + refetchOnMount: true, + staleTime: 30000, // Consider data stale after 25 seconds ...options, }, queryClient, diff --git a/frontend/src/app/api/queries/useProviderHealthQuery.ts b/frontend/src/app/api/queries/useProviderHealthQuery.ts index d4038cfc..82ca2db2 100644 --- a/frontend/src/app/api/queries/useProviderHealthQuery.ts +++ b/frontend/src/app/api/queries/useProviderHealthQuery.ts @@ -92,6 +92,13 @@ export const useProviderHealthQuery = ( queryKey: ["provider", "health"], queryFn: checkProviderHealth, retry: false, // Don't retry health checks automatically + refetchInterval: (query) => { + // If healthy, check every 30 seconds; otherwise check every 3 seconds + return query.state.data?.status === "healthy" ? 30000 : 3000; + }, + refetchOnWindowFocus: true, + refetchOnMount: true, + staleTime: 30000, // Consider data stale after 25 seconds enabled: !!settings?.edited && options?.enabled !== false, // Only run after onboarding is complete ...options, }, diff --git a/src/tui/managers/docling_manager.py b/src/tui/managers/docling_manager.py index e58a5b1e..109cb7c1 100644 --- a/src/tui/managers/docling_manager.py +++ b/src/tui/managers/docling_manager.py @@ -34,6 +34,7 @@ class DoclingManager: # Bind to all interfaces by default (can be overridden with DOCLING_BIND_HOST env var) self._host = os.getenv('DOCLING_BIND_HOST', '0.0.0.0') self._running = False + self._starting = False self._external_process = False # PID file to track docling-serve across sessions (in current working directory) @@ -126,6 +127,7 @@ class DoclingManager: if self._process is not None and self._process.poll() is None: self._running = True self._external_process = False + self._starting = False # Clear starting flag if service is running return True # Check if we have a PID from file @@ -133,6 +135,7 @@ class DoclingManager: if pid is not None and self._is_process_running(pid): self._running = True self._external_process = True + self._starting = False # Clear starting flag if service is running return True # No running process found @@ -142,6 +145,19 @@ class DoclingManager: def get_status(self) -> Dict[str, Any]: """Get current status of docling serve.""" + # Check for starting state first + if self._starting: + display_host = "localhost" if self._host == "0.0.0.0" else self._host + return { + "status": "starting", + "port": self._port, + "host": self._host, + "endpoint": None, + "docs_url": None, + "ui_url": None, + "pid": None + } + if self.is_running(): # Try to get PID from process handle first, then from PID file pid = None @@ -196,6 +212,9 @@ class DoclingManager: except Exception as e: self._add_log_entry(f"Error checking port availability: {e}") + # Set starting flag to show "Starting" status in UI + self._starting = True + # Clear log buffer when starting self._log_buffer = [] self._add_log_entry("Starting docling serve as external process...") @@ -261,6 +280,8 @@ class DoclingManager: if result == 0: self._add_log_entry(f"Docling-serve is now listening on {self._host}:{self._port}") + # Service is now running, clear starting flag + self._starting = False break except: pass @@ -294,16 +315,24 @@ class DoclingManager: self._add_log_entry(f"Error reading final output: {e}") self._running = False + self._starting = False return False, f"Docling serve process exited immediately (code: {return_code})" + # If we get here and the process is still running but not listening yet, + # clear the starting flag anyway (it's running, just not ready) + if self._process.poll() is None: + self._starting = False + display_host = "localhost" if self._host == "0.0.0.0" else self._host return True, f"Docling serve starting on http://{display_host}:{port}" except FileNotFoundError: + self._starting = False return False, "docling-serve not available. Please install: uv add docling-serve" except Exception as e: self._running = False self._process = None + self._starting = False return False, f"Error starting docling serve: {str(e)}" def _start_output_capture(self): diff --git a/src/tui/screens/monitor.py b/src/tui/screens/monitor.py index 91df51f6..01c243c6 100644 --- a/src/tui/screens/monitor.py +++ b/src/tui/screens/monitor.py @@ -206,10 +206,21 @@ class MonitorScreen(Screen): # Add docling serve to its own table docling_status = self.docling_manager.get_status() - docling_running = docling_status["status"] == "running" - docling_status_text = "running" if docling_running else "stopped" - docling_style = "bold green" if docling_running else "bold red" - docling_port = f"{docling_status['host']}:{docling_status['port']}" if docling_running else "N/A" + docling_status_value = docling_status["status"] + docling_running = docling_status_value == "running" + docling_starting = docling_status_value == "starting" + + if docling_running: + docling_status_text = "running" + docling_style = "bold green" + elif docling_starting: + docling_status_text = "starting" + docling_style = "bold yellow" + else: + docling_status_text = "stopped" + docling_style = "bold red" + + docling_port = f"{docling_status['host']}:{docling_status['port']}" if (docling_running or docling_starting) else "N/A" docling_pid = str(docling_status.get("pid")) if docling_status.get("pid") else "N/A" if self.docling_table: @@ -375,15 +386,25 @@ class MonitorScreen(Screen): """Start docling serve.""" self.operation_in_progress = True try: - success, message = await self.docling_manager.start() + # Start the service (this sets _starting = True internally at the start) + # Create task and let it begin executing (which sets the flag) + start_task = asyncio.create_task(self.docling_manager.start()) + # Give it a tiny moment to set the _starting flag + await asyncio.sleep(0.1) + # Refresh immediately to show "Starting" status + await self._refresh_services() + # Now wait for start to complete + success, message = await start_task if success: self.notify(message, severity="information") else: self.notify(f"Failed to start docling serve: {message}", severity="error") - # Refresh the services table to show updated status + # Refresh again to show final status (running or stopped) await self._refresh_services() except Exception as e: self.notify(f"Error starting docling serve: {str(e)}", severity="error") + # Refresh on error to clear starting status + await self._refresh_services() finally: self.operation_in_progress = False @@ -646,7 +667,11 @@ class MonitorScreen(Screen): suffix = f"-{random.randint(10000, 99999)}" # Add docling serve controls - docling_running = self.docling_manager.is_running() + docling_status = self.docling_manager.get_status() + docling_status_value = docling_status["status"] + docling_running = docling_status_value == "running" + docling_starting = docling_status_value == "starting" + if docling_running: docling_controls.mount( Button("Stop", variant="error", id=f"docling-stop-btn{suffix}") @@ -654,6 +679,11 @@ class MonitorScreen(Screen): docling_controls.mount( Button("Restart", variant="primary", id=f"docling-restart-btn{suffix}") ) + elif docling_starting: + # Show disabled button or no button when starting + start_btn = Button("Starting...", variant="warning", id=f"docling-start-btn{suffix}") + start_btn.disabled = True + docling_controls.mount(start_btn) else: docling_controls.mount( Button("Start", variant="success", id=f"docling-start-btn{suffix}") From 4b9d7599fce16a91a48cb7feda9b89d10f1b2f09 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:42:47 -0300 Subject: [PATCH 2/2] fix: add Thinking state to agent response, fix ollama and watsonx overwriting values on the onboarding (#405) * Added thinking message to assistant message * fixed ibm and ollama overwriting values --- .../app/chat/components/assistant-message.tsx | 6 ++++-- frontend/src/app/globals.css | 18 ++++++++++++++++++ .../onboarding/components/ibm-onboarding.tsx | 2 +- .../components/ollama-onboarding.tsx | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/chat/components/assistant-message.tsx b/frontend/src/app/chat/components/assistant-message.tsx index 9b109813..0f24dd8c 100644 --- a/frontend/src/app/chat/components/assistant-message.tsx +++ b/frontend/src/app/chat/components/assistant-message.tsx @@ -83,8 +83,10 @@ export function AssistantMessage({ )} chatMessage={ isStreaming - ? content + - ' ' + ? (content.trim() + ? content + + ' ' + : 'Thinking') : content } /> diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 7ffab80e..4765ef8c 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -365,4 +365,22 @@ width: 100%; height: 30px; } + + .thinking-dots::after { + content: "."; + animation: thinking-dots 1.4s steps(3, end) infinite; + } + + @keyframes thinking-dots { + 0% { + content: "."; + } + 33.33% { + content: ".."; + } + 66.66%, + 100% { + content: "..."; + } + } } diff --git a/frontend/src/app/onboarding/components/ibm-onboarding.tsx b/frontend/src/app/onboarding/components/ibm-onboarding.tsx index d3540977..3d480248 100644 --- a/frontend/src/app/onboarding/components/ibm-onboarding.tsx +++ b/frontend/src/app/onboarding/components/ibm-onboarding.tsx @@ -26,7 +26,7 @@ export function IBMOnboarding({ setIsLoadingModels?: (isLoading: boolean) => void; alreadyConfigured?: boolean; }) { - const [endpoint, setEndpoint] = useState("https://us-south.ml.cloud.ibm.com"); + const [endpoint, setEndpoint] = useState(alreadyConfigured ? "" : "https://us-south.ml.cloud.ibm.com"); const [apiKey, setApiKey] = useState(""); const [projectId, setProjectId] = useState(""); diff --git a/frontend/src/app/onboarding/components/ollama-onboarding.tsx b/frontend/src/app/onboarding/components/ollama-onboarding.tsx index e9d2fa1b..99c26d2a 100644 --- a/frontend/src/app/onboarding/components/ollama-onboarding.tsx +++ b/frontend/src/app/onboarding/components/ollama-onboarding.tsx @@ -25,7 +25,7 @@ export function OllamaOnboarding({ isEmbedding?: boolean; alreadyConfigured?: boolean; }) { - const [endpoint, setEndpoint] = useState(`http://localhost:11434`); + const [endpoint, setEndpoint] = useState(alreadyConfigured ? undefined : `http://localhost:11434`); const [showConnecting, setShowConnecting] = useState(false); const debouncedEndpoint = useDebouncedValue(endpoint, 500);