1414from websocket import WebSocket , WebSocketException , WebSocketTimeoutException
1515
1616from comfy_cli .env_checker import check_comfy_server_running
17+ from comfy_cli .workflow_to_api import WorkflowConversionError , convert_ui_to_api
1718from comfy_cli .workspace_manager import WorkspaceManager
1819
1920workspace_manager = WorkspaceManager ()
@@ -37,55 +38,31 @@ def _validate_api_workflow(workflow):
3738 return workflow
3839
3940
40- class WorkflowConverterUnavailable ( Exception ) :
41- """The running ComfyUI server doesn't expose /workflow/convert."""
41+ def fetch_object_info ( host : str , port : int , timeout : int ) -> dict :
42+ """GET ``/object_info`` from the running ComfyUI server.
4243
43-
44- def convert_ui_workflow_via_server (workflow : dict , host : str , port : int , timeout : int ) -> dict :
45- """POST a UI-format workflow to the server's /workflow/convert and return API-format JSON.
46-
47- Raises WorkflowConverterUnavailable if the server doesn't expose the endpoint.
48- Raises typer.Exit on other conversion failures.
44+ The response describes every loaded node class's input schema and is what
45+ the converter uses to map widget values to input names, fill defaults, etc.
4946 """
50- url = f"http://{ host } :{ port } /workflow/convert"
51- req = request .Request (url , json .dumps (workflow ).encode ("utf-8" ))
52- req .add_header ("Content-Type" , "application/json" )
47+ url = f"http://{ host } :{ port } /object_info"
5348 try :
54- resp = request .urlopen (req , timeout = timeout )
49+ with request .urlopen (url , timeout = timeout ) as resp :
50+ body = resp .read ()
5551 except urllib .error .HTTPError as e :
56- if e .code in (404 , 405 ):
57- raise WorkflowConverterUnavailable () from e
5852 body = e .read ().decode ("utf-8" , errors = "replace" ).strip ()
59- pprint (f"[bold red]Workflow conversion failed (HTTP { e .code } ): { body [:500 ]} [/bold red]" )
53+ pprint (f"[bold red]Failed to fetch /object_info (HTTP { e .code } ): { body [:500 ]} [/bold red]" )
6054 raise typer .Exit (code = 1 ) from e
6155 except urllib .error .URLError as e :
62- pprint (f"[bold red]Workflow conversion failed: { e .reason } [/bold red]" )
56+ pprint (f"[bold red]Failed to fetch /object_info: { e .reason } [/bold red]" )
57+ raise typer .Exit (code = 1 ) from e
58+ except TimeoutError as e :
59+ pprint (f"[bold red]Failed to fetch /object_info: timed out after { timeout } s[/bold red]" )
6360 raise typer .Exit (code = 1 ) from e
6461 try :
65- converted = json .loads (resp . read () )
62+ return json .loads (body )
6663 except json .JSONDecodeError as e :
67- pprint ("[bold red]Workflow conversion failed : server returned invalid JSON[/bold red]" )
64+ pprint ("[bold red]Failed to fetch /object_info : server returned invalid JSON[/bold red]" )
6865 raise typer .Exit (code = 1 ) from e
69- if not isinstance (converted , dict ) or not converted :
70- pprint ("[bold red]Workflow conversion failed: expected a non-empty JSON object[/bold red]" )
71- raise typer .Exit (code = 1 )
72- first = converted [next (iter (converted ))]
73- if not isinstance (first , dict ) or "class_type" not in first :
74- pprint ("[bold red]Workflow conversion failed: returned data is not API workflow format[/bold red]" )
75- raise typer .Exit (code = 1 )
76- return converted
77-
78-
79- def _print_converter_unavailable_help () -> None :
80- pprint (
81- "[bold red]This ComfyUI server doesn't expose a /workflow/convert endpoint[/bold red]\n "
82- "[bold red]to convert it to API format.[/bold red]\n "
83- "\n "
84- "[yellow]Workarounds:[/yellow]\n "
85- "[yellow] * Install a custom node that adds /workflow/convert on the server[/yellow]\n "
86- "[yellow] * Or, in the ComfyUI frontend, use 'File > Export (API)' to save[/yellow]\n "
87- "[yellow] your workflow as API format[/yellow]"
88- )
8966
9067
9168def execute (workflow : str , host , port , wait = True , verbose = False , local_paths = False , timeout = 30 ):
@@ -112,11 +89,29 @@ def execute(workflow: str, host, port, wait=True, verbose=False, local_paths=Fal
11289 raise typer .Exit (code = 1 ) from e
11390
11491 if is_ui_workflow (raw_workflow ):
115- pprint ("[yellow]Detected UI-format workflow, converting via server's /workflow/convert...[/yellow]" )
92+ pprint ("[yellow]Detected UI-format workflow, converting to API format...[/yellow]" )
93+ object_info = fetch_object_info (host , port , timeout )
11694 try :
117- workflow = convert_ui_workflow_via_server (raw_workflow , host , port , timeout )
118- except WorkflowConverterUnavailable :
119- _print_converter_unavailable_help ()
95+ workflow = convert_ui_to_api (raw_workflow , object_info )
96+ except WorkflowConversionError as e :
97+ pprint (f"[bold red]Workflow conversion failed: { e } [/bold red]" )
98+ raise typer .Exit (code = 1 ) from e
99+ except Exception as e :
100+ # The converter is experimental; an unexpected crash here is a bug
101+ # in our code, not user error. Show a clean message and a pointer.
102+ pprint (
103+ f"[bold red]Workflow conversion crashed unexpectedly: { type (e ).__name__ } : { e } [/bold red]\n "
104+ "[yellow]The UI-to-API converter is experimental. Please report this at[/yellow]\n "
105+ "[yellow] https://github.com/Comfy-Org/comfy-cli/issues[/yellow]\n "
106+ "[yellow]and attach the workflow file if possible.[/yellow]"
107+ )
108+ if verbose :
109+ import traceback
110+
111+ traceback .print_exc ()
112+ raise typer .Exit (code = 1 ) from e
113+ if not workflow :
114+ pprint ("[bold red]Workflow conversion produced no executable nodes[/bold red]" )
120115 raise typer .Exit (code = 1 )
121116 else :
122117 workflow = _validate_api_workflow (raw_workflow )
0 commit comments