diff --git a/distiller_cm5_python/client/mid_layer/llm_client.py b/distiller_cm5_python/client/mid_layer/llm_client.py index bc7c982..a992836 100644 --- a/distiller_cm5_python/client/mid_layer/llm_client.py +++ b/distiller_cm5_python/client/mid_layer/llm_client.py @@ -273,9 +273,10 @@ def __init__( if ( not self._check_cloud_api_connection_sync() ): # Use sync check during init - # logger.error(f"LLMClient.__init__: Could not connect to API at {self.server_url}") - raise UserVisibleError( - f"Could not connect to API at {self.server_url}. Check URL and API key." + # Log a warning, but don't raise an error immediately. + # Let the first actual request fail if connection is terrible. + logger.warning( + f"LLMClient.__init__: Initial check failed for {self.provider_type} API at {self.server_url}. Client will proceed, but requests may fail." ) else: logger.error( @@ -448,39 +449,6 @@ def _check_cloud_api_connection_sync(self) -> bool: logger.error(f"Unexpected error during sync cloud connection check: {e}") return False - # Async version for internal use during API calls if needed later (currently unused) - async def _check_cloud_api_connection_async(self) -> bool: - """Asynchronously check connection for cloud-based APIs (e.g., OpenRouter).""" - endpoint = self._get_endpoint(self.models_url) - headers = self._get_headers() - if not headers.get("Authorization"): - logger.error( - f"Cannot check connection async to {self.server_url}: API key is missing." - ) - return False - - try: - async with aiohttp.ClientSession() as session: - async with session.get( - endpoint, timeout=10, headers=headers - ) as response: - if response.status == 200: - return True - else: - response_text = await response.text() - logger.warning( - f"Async cloud API connection check failed at {endpoint}. Status: {response.status}, Response: {response_text[:100]}..." - ) - return False - except aiohttp.ClientError as e: - logger.warning( - f"Async cloud API connection check failed at {endpoint}. Error: {e}" - ) - return False - except Exception as e: - logger.error(f"Unexpected error during async cloud connection check: {e}") - return False - def _get_headers(self) -> Dict[str, str]: """Get headers for API requests, including Authorization if api_key is set.""" headers = {"Content-Type": "application/json"} diff --git a/distiller_cm5_python/client/mid_layer/mcp_client.py b/distiller_cm5_python/client/mid_layer/mcp_client.py index b1cebf1..39195ed 100644 --- a/distiller_cm5_python/client/mid_layer/mcp_client.py +++ b/distiller_cm5_python/client/mid_layer/mcp_client.py @@ -217,9 +217,39 @@ async def connect_to_server(self, server_script_path: str) -> bool: ) return False else: - # For other provider types, just set connected - self._is_connected = True - return True + # For other provider types, explicitly check connection + logger.info( + f"Verifying connection for LLM provider: {self.llm_provider.provider_type} at {self.llm_provider.server_url}" + ) + if self.llm_provider.check_connection(): + logger.info( + f"Successfully connected to LLM provider: {self.llm_provider.provider_type}" + ) + self._is_connected = True + if self.dispatcher: + self.dispatcher.dispatch( + StatusEvent( + type=EventType.INFO, + content=f"Successfully connected to LLM provider: {self.llm_provider.provider_type}", + status=StatusType.SUCCESS, + component="llm_connection", + ) + ) + return True + else: + error_msg = f"Failed to connect to LLM provider: {self.llm_provider.provider_type} at {self.llm_provider.server_url}. Please check configuration and network." + logger.error(error_msg) + if self.dispatcher: + self.dispatcher.dispatch( + StatusEvent( + type=EventType.ERROR, + content=error_msg, + status=StatusType.FAILED, + component="llm_connection", + ) + ) + self._is_connected = False + return False except Exception as e: logger.error(f"Failed to connect to server: {e}") diff --git a/distiller_cm5_python/client/ui/App.py b/distiller_cm5_python/client/ui/App.py index 8706211..69ad944 100644 --- a/distiller_cm5_python/client/ui/App.py +++ b/distiller_cm5_python/client/ui/App.py @@ -180,16 +180,18 @@ async def initialize(self): self._apply_window_constraints() # E-Ink Initialization Call self._init_eink_renderer() - self._eink_initialized = True - # Add a small delay to allow QML/FocusManager to potentially settle - await asyncio.sleep(0.2) # Wait 200ms - logger.info("Proceeding to start input monitor after short delay.") + if self._eink_initialized: # Check if E-Ink initialized successfully + # Add a small delay to allow QML/FocusManager to potentially settle + await asyncio.sleep(0.2) # Wait 200ms + logger.info("Proceeding to start input monitor after short delay.") - # Set the target window for the input monitor - self.input_monitor.set_target_window(self.main_window) - # Start the input monitor with default device name - self.input_monitor.start() + # Set the target window for the input monitor + self.input_monitor.set_target_window(self.main_window) + # Start the input monitor with default device name + self.input_monitor.start() + else: + logger.warning("E-Ink initialization failed (self._eink_initialized is False). Input monitor will not be started.") logger.info("Application initialized successfully") @@ -527,13 +529,9 @@ def _init_eink_renderer(self): # Set adaptive capture mode self.eink_renderer.set_adaptive_capture(adaptive_capture) - # Connect the signal to an async lambda that schedules the handler - # Use asyncio.create_task to run the async handler without blocking the signal emission - self.eink_renderer.frameReady.connect( - lambda data, w, h: asyncio.create_task( - self._handle_eink_frame(data, w, h) - ) - ) + # Connect the signal to a synchronous handler that uses Qt's thread pool + # This avoids threading issues with asyncio.create_task + self.eink_renderer.frameReady.connect(self._handle_eink_frame_sync) # Start capturing frames self.eink_renderer.start() @@ -558,10 +556,10 @@ def _init_eink_renderer(self): self.eink_renderer = None return False - async def _handle_eink_frame(self, frame_data, width, height): + def _handle_eink_frame_sync(self, frame_data, width, height): """ - Handle a new frame from the E-Ink renderer asynchronously. - This method forwards the frame to the e-ink bridge for display in a separate thread. + Handle a new frame from the E-Ink renderer synchronously. + This method forwards the frame to the e-ink bridge for display in the main thread. Args: frame_data: The binary data for the frame @@ -572,17 +570,15 @@ async def _handle_eink_frame(self, frame_data, width, height): f"E-Ink frame received: {width}x{height}, {len(frame_data)} bytes. Offloading to bridge." ) - # Forward the frame to the e-ink bridge if available, using a separate thread + # Forward the frame to the e-ink bridge if available, using the main thread if self.eink_bridge and self.eink_bridge.initialized: try: - # Run the potentially blocking bridge call in a separate thread - await asyncio.to_thread( - self.eink_bridge.handle_frame, frame_data, width, height - ) + # Run the potentially blocking bridge call in the main thread + self.eink_bridge.handle_frame(frame_data, width, height) logger.debug("E-Ink frame successfully handled by bridge.") except Exception as e: logger.error( - f"Error calling eink_bridge.handle_frame in thread: {e}", + f"Error calling eink_bridge.handle_frame in main thread: {e}", exc_info=True, ) else: diff --git a/main.py b/main.py index e5235f8..6aeba9b 100755 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ import sys import os import logging +import time from typing import Optional # Add project root to sys.path if necessary, although cli.py should handle it @@ -68,7 +69,9 @@ async def main(): else: logger.info(f"Existing llama-cpp server detected at {SERVER_URL}. Proceeding.") # --- End Llama.cpp Server Management --- - + else: + logger.info("Configuration specifies non-llama-cpp provider. Skipping server management.") + time.sleep(5) # Wait for 5 seconds to ensure the server is ready if hasattr(args, 'gui') and args.gui: logger.info("Starting GUI mode...")