Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
42 changes: 23 additions & 19 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def signal_handler(sig: int, frame: FrameType | None) -> None:

Cleans up WebDriver and Ollama resources and exits the application properly.
"""
print('\nShutting down gracefully...')
print("\nShutting down gracefully...")
# Clean up WebDriverManager
if app.webdriver_manager is not None:
try:
Expand All @@ -28,6 +28,7 @@ def signal_handler(sig: int, frame: FrameType | None) -> None:
# Clean up Ollama server if we started it
try:
from static.providers.local.ollama_api import OllamaAPI

OllamaAPI.stop_server()
except Exception as e:
print(f"Error stopping Ollama: {e}")
Expand All @@ -42,54 +43,55 @@ def signal_handler(sig: int, frame: FrameType | None) -> None:
# Register signal handler at module level for both run modes
signal.signal(signal.SIGINT, signal_handler)

if __name__ == '__main__':
if __name__ == "__main__":
"""Main execution block.

Starts Flask server in a daemon thread, initializes WebDriver for vision system,
and maintains the main thread for graceful interrupt handling.
"""
# Parse command-line arguments
parser = argparse.ArgumentParser(description='MatHud Flask Application')
parser = argparse.ArgumentParser(description="MatHud Flask Application")
parser.add_argument(
'-p', '--port',
type=int,
default=None,
help='Port to run the server on (default: 5000, or PORT env var)'
"-p", "--port", type=int, default=None, help="Port to run the server on (default: 5000, or PORT env var)"
)
args = parser.parse_args()

try:
# Priority: CLI argument > environment variable > default (5000)
env_port = os.environ.get('PORT')
env_port = os.environ.get("PORT")
port = args.port if args.port is not None else int(env_port or 5000)

# Store port in app config for WebDriverManager to use
app.config['SERVER_PORT'] = port
app.config["SERVER_PORT"] = port

# Check if we're running in a deployment environment
is_deployed = args.port is None and env_port is not None
force_non_debug = os.environ.get('MATHUD_NON_DEBUG', '').lower() in ('1', 'true', 'yes')
force_non_debug = os.environ.get("MATHUD_NON_DEBUG", "").lower() in ("1", "true", "yes")

# Enable debug mode for local development
debug_mode = not (is_deployed or force_non_debug)

if is_deployed:
# For deployment: run Flask directly without threading
host = '0.0.0.0' # Bind to all interfaces for deployment
host = "0.0.0.0" # Bind to all interfaces for deployment
print(f"Starting Flask app on {host}:{port} (deployment mode)")
app.run(host=host, port=port, debug=False)
else:
# For local development: use threading approach with debug capability
host = '127.0.0.1' # Localhost for development
host = "127.0.0.1" # Localhost for development
print(f"Starting Flask app on {host}:{port} (development mode, debug={debug_mode})")

from threading import Thread
server = Thread(target=app.run, kwargs={
'host': host,
'port': port,
'debug': debug_mode,
'use_reloader': False # Disable reloader in thread mode to avoid issues
})

server = Thread(
target=app.run,
kwargs={
"host": host,
"port": port,
"debug": debug_mode,
"use_reloader": False, # Disable reloader in thread mode to avoid issues
},
)
server.daemon = True # Make the server thread a daemon so it exits when main thread exits
server.start()

Expand All @@ -99,6 +101,7 @@ def signal_handler(sig: int, frame: FrameType | None) -> None:
# Start Ollama server if installed (only in local development)
try:
from static.providers.local.ollama_api import OllamaAPI

if OllamaAPI.is_ollama_installed():
success, message = OllamaAPI.start_server(timeout=10)
print(f"Ollama: {message}")
Expand All @@ -110,8 +113,9 @@ def signal_handler(sig: int, frame: FrameType | None) -> None:
# Initialize WebDriver (only in local development)
if app.webdriver_manager is None:
import requests

try:
requests.get(f'http://{host}:{port}/init_webdriver')
requests.get(f"http://{host}:{port}/init_webdriver")
print("WebDriver initialized successfully")
except Exception as e:
print(f"Failed to initialize WebDriver: {str(e)}")
Expand Down
12 changes: 3 additions & 9 deletions cli/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ def setup(self) -> None:
options.add_argument("--remote-debugging-pipe")
profiles_root = runtime_root / "profiles"
profiles_root.mkdir(parents=True, exist_ok=True)
self._profile_dir = Path(
tempfile.mkdtemp(prefix="chrome-profile-", dir=str(profiles_root))
)
self._profile_dir = Path(tempfile.mkdtemp(prefix="chrome-profile-", dir=str(profiles_root)))
options.add_argument(f"--user-data-dir={self._profile_dir}")

if platform.machine() in ("aarch64", "arm64"):
Expand Down Expand Up @@ -266,9 +264,7 @@ def wait_for_element(
raise RuntimeError("Browser not initialized. Call setup() first.")

try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((by, selector))
)
WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located((by, selector)))
return True
except Exception:
return False
Expand Down Expand Up @@ -313,9 +309,7 @@ def get_canvas_state(self) -> dict[str, Any]:
Returns:
Canvas state dictionary.
"""
result = self.execute_js(
"return window._canvas ? JSON.stringify(window._canvas.get_state()) : null"
)
result = self.execute_js("return window._canvas ? JSON.stringify(window._canvas.get_state()) : null")
if result:
parsed: dict[str, Any] = json.loads(result)
return parsed
Expand Down
1 change: 1 addition & 0 deletions cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
# CLI output directory (for screenshots, etc.)
CLI_OUTPUT_DIR = Path(__file__).parent / "output"


# Python interpreter path
def get_python_path() -> Path:
"""Get the path to the Python interpreter in the virtual environment."""
Expand Down
7 changes: 3 additions & 4 deletions cli/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,7 @@ def start(
)
return (
False,
f"Port {self.port} is already in use. "
f"Choose another port or stop the existing listener.",
f"Port {self.port} is already in use. Choose another port or stop the existing listener.",
)
else:
owner_pid = self._find_listener_pid_on_port()
Expand All @@ -275,8 +274,7 @@ def start(
)
return (
False,
f"Port {self.port} is already in use. "
f"Choose another port or stop the existing listener.",
f"Port {self.port} is already in use. Choose another port or stop the existing listener.",
)

# Set environment to disable auth for CLI operations
Expand Down Expand Up @@ -451,6 +449,7 @@ def status(port: int, as_json: bool) -> None:

if as_json:
import json

click.echo(json.dumps(info))
else:
if info["running"]:
Expand Down
6 changes: 5 additions & 1 deletion cli/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ def all_cmd(port: int, with_auth: bool, start_server: bool, skip_lint: bool) ->
raise SystemExit(1)

lint_label = "Lint + " if not skip_lint else ""
click.echo(click.style(f"\nAll passed! ({lint_label}Server + {results.get('tests_run', 0)} client tests)", fg="green", bold=True))
click.echo(
click.style(
f"\nAll passed! ({lint_label}Server + {results.get('tests_run', 0)} client tests)", fg="green", bold=True
)
)
if results.get("screenshot"):
click.echo(f"\nScreenshot saved to: {results['screenshot']}")
Loading
Loading