@@ -18,6 +18,44 @@ def __init__(self):
1818 Path (__file__ ).parent .parent / "assets" / "workflow-renderer" / "index.html"
1919 )
2020 self .workflows_dir = Path (__file__ ).parent .parent / "assets" / "workflows"
21+ self ._browser = None
22+ self ._page = None
23+ self ._playwright = None
24+
25+ async def __init_browser (self ):
26+ """
27+ Initialize browser and load the workflow renderer page just once
28+
29+ Context: editor.js is 20MB, (yes, you read correct) and
30+ we need to cached if not the timeouts happens)
31+ """
32+
33+ if self ._browser is None :
34+ logger .info ("Initializing browser..." )
35+ self ._playwright = await async_playwright ().start ()
36+ self ._browser = await self ._playwright .chromium .launch ()
37+ self ._page = await self ._browser .new_page ()
38+
39+ self ._page .on (
40+ "console" ,
41+ lambda msg : logger .info (f"Browser console [{ msg .type } ]: { msg .text } " ),
42+ )
43+ self ._page .on (
44+ "pageerror" , lambda err : logger .error (f"Browser error: { err } " )
45+ )
46+
47+ logger .info (f"Loading HTML file: file://{ self .html_path .absolute ()} " )
48+ await self ._page .goto (f"file://{ self .html_path .absolute ()} " )
49+
50+ # Wait for editor to initialize
51+ logger .info ("Waiting for editor to initialize..." )
52+ await self ._page .wait_for_function ("typeof render_workflow === 'function'" )
53+ await self ._page .wait_for_function ("ready(); EditorIsReady === true" )
54+ logger .info ("Browser initialized and page loaded" )
55+
56+ @property
57+ def browser (self ):
58+ return self ._browser
2159
2260 async def render_workflow_to_png_file (self , workflow_data : str ) -> str :
2361 """
@@ -56,70 +94,40 @@ async def render_workflow_to_svg(self, workflow_data: str) -> str:
5694 str: SVG content
5795 """
5896 logger .info ("Starting workflow rendering process..." )
59- logger .info (f"HTML path: { self .html_path .absolute ()} " )
6097
61- async with async_playwright () as p :
62- logger .info ("Launching headless browser..." )
63- browser = await p .chromium .launch ()
64- page = await browser .new_page ()
98+ await self .__init_browser ()
6599
66- page .on (
67- "console" ,
68- lambda msg : logger .info (f"Browser console [{ msg .type } ]: { msg .text } " ),
100+ try :
101+ await self ._page .evaluate (f"""
102+ let workflow_data = { workflow_data } ;
103+ render_workflow(
104+ document.getElementById("renderWorkflow"),
105+ JSON.stringify(workflow_data)
106+ );
107+ """ )
108+
109+ # Get the SVG content
110+ await self ._page .wait_for_function (
111+ "document.getElementById('renderWorkflow')."
112+ "querySelector('svg') !== null"
69113 )
70- page .on ("pageerror" , lambda err : logger .error (f"Browser error: { err } " ))
71-
72- # Load the HTML file
73- logger .info (f"Loading HTML file: file://{ self .html_path .absolute ()} " )
74- await page .goto (f"file://{ self .html_path .absolute ()} " )
75-
76- # Wait for editor to initialize
77- logger .info ("Waiting for editor to initialize..." )
78- await page .wait_for_function ("typeof render_workflow === 'function'" )
79- await page .wait_for_function ("ready(); EditorIsReady === true" )
80-
81- # Execute the workflow rendering on page load
82- try :
83- # Example rendering
84- # await page.evaluate("""
85- # render_workflow(
86- # document.getElementById("renderWorkflow"),
87- # JSON.stringify(sample_data)
88- # );
89- # """)
90-
91- await page .evaluate (f"""
92- let workflow_data = { workflow_data } ;
93- render_workflow(
94- document.getElementById("renderWorkflow"),
95- JSON.stringify(workflow_data)
96- );
97- """ )
98-
99- # Get the SVG content
100- await page .wait_for_function (
101- "document.getElementById('renderWorkflow')."
102- "querySelector('svg') !== null"
103- )
104- svg_content = await page .evaluate (
105- "document.getElementById('renderWorkflow').innerHTML"
106- )
107- except Exception as e :
108- logger .error (f"Error calling render_workflow: { e } " )
109- # Try to get any error messages from the page
110- errors = await page .evaluate (
111- "document.querySelector('#renderWorkflow').innerHTML"
112- )
113- logger .info (f"Container content: { errors } " )
114- raise
115-
116- logger .info (
117- f"SVG generated, length: "
118- f"{ len (svg_content ) if svg_content else 0 } characters"
114+ svg_content = await self ._page .evaluate (
115+ "document.getElementById('renderWorkflow').innerHTML"
116+ )
117+ except Exception as e :
118+ logger .error (f"Error calling render_workflow: { e } " )
119+ # Try to get any error messages from the page
120+ errors = await self ._page .evaluate (
121+ "document.querySelector('#renderWorkflow').innerHTML"
119122 )
120- await browser .close ()
121- logger .info ("Browser closed" )
122- return svg_content
123+ logger .info (f"Container content: { errors } " )
124+ raise
125+
126+ logger .info (
127+ f"SVG generated, length: "
128+ f"{ len (svg_content ) if svg_content else 0 } characters"
129+ )
130+ return svg_content
123131
124132
125133@orchestrator_mcp .tool ()
0 commit comments