diff --git a/src/conductor/cli/explorer.py b/src/conductor/cli/explorer.py index e58cfe1..9ba4377 100644 --- a/src/conductor/cli/explorer.py +++ b/src/conductor/cli/explorer.py @@ -20,13 +20,20 @@ def register_command(subparsers): default=8000, help="The port to listen on when serving the explorer.", ) + parser.add_argument( + "--no-browser", + action="store_true", + help="If set, do not automatically start the web browser.", + ) parser.set_defaults(func=main) @cli_command def main(args): try: - start_explorer(host=args.host, port=args.port) + start_explorer( + host=args.host, port=args.port, launch_browser=not args.no_browser + ) except ConductorAbort: # We ignore this exception because it is raised when the user hits # Ctrl+C to shut down the server. diff --git a/src/conductor/explorer/explorer.py b/src/conductor/explorer/explorer.py index fcecf66..db7c5e5 100644 --- a/src/conductor/explorer/explorer.py +++ b/src/conductor/explorer/explorer.py @@ -1,7 +1,10 @@ +import time +import webbrowser +import threading from conductor.errors import MissingExplorerSupport -def start_explorer(host: str, port: int) -> None: +def start_explorer(host: str, port: int, launch_browser: bool) -> None: """ Entrypoint to launching Conductor's explorer. This function will attempt to start the explorer. If the user has not installed the necessary @@ -12,14 +15,41 @@ def start_explorer(host: str, port: int) -> None: # user has not installed Conductor's UI dependencies. try: import uvicorn - - uvicorn_config = uvicorn.Config( - "conductor.explorer.routes:app", - host=host, - port=port, - log_level="info", - ) - server = uvicorn.Server(uvicorn_config) - server.run() except ImportError as ex: raise MissingExplorerSupport() from ex + + uvicorn_config = uvicorn.Config( + "conductor.explorer.routes:app", + host=host, + port=port, + log_level="info", + ) + server = uvicorn.Server(uvicorn_config) + + # Start the server and launch the browser if requested. + try: + if launch_browser: + launch_thread = threading.Thread( + target=_launch_browser, args=(host, port, 1) + ) + launch_thread.start() + else: + launch_thread = None + + server.run() + finally: + if launch_thread is not None: + launch_thread.join() + + +def _launch_browser(host: str, port: int, wait_s: int) -> None: + """ + Launches the user's default browser to the Conductor explorer's URL. + """ + # We want to start the web browser after the server has started. + # Unfortunately, uvicorn does not provide any nice "callback"-like + # mechanisms to know when it has started. So we use this heuristic approach + # of waiting for a few seconds. This is ugly, but keeps things simple + # without overengineering something meant to be cosmetic. + time.sleep(wait_s) + webbrowser.open(f"http://{host}:{port}") diff --git a/website/docs/cli/explorer.md b/website/docs/cli/explorer.md index 8278ac1..226b76a 100644 --- a/website/docs/cli/explorer.md +++ b/website/docs/cli/explorer.md @@ -23,6 +23,12 @@ will be port 8000. The host that Conductor's explorer tool will bind to. If unspecified, this will be `localhost`. Generally, you will not need to modify this value. +### `--no-browser` + +If set, Conductor will not automatically launch a web browser. By default, this +explorer command will start a web browser pointed at the explorer's web +interface. + ### `-h` or `--help` Prints a help message that provides details about how to use the `cond explorer`