mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-03-22 03:38:14 +00:00
Compare commits
1 Commits
main
...
devin/1774
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4b22e92ad |
@@ -3153,12 +3153,19 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
else:
|
||||
logger.warning(message)
|
||||
|
||||
def plot(self, filename: str = "crewai_flow.html", show: bool = True) -> str:
|
||||
def plot(
|
||||
self,
|
||||
filename: str = "crewai_flow.html",
|
||||
show: bool = True,
|
||||
output_dir: str | None = None,
|
||||
) -> str:
|
||||
"""Create interactive HTML visualization of Flow structure.
|
||||
|
||||
Args:
|
||||
filename: Output HTML filename (default: "crewai_flow.html").
|
||||
show: Whether to open in browser (default: True).
|
||||
output_dir: Directory to save generated files. Defaults to the
|
||||
current working directory.
|
||||
|
||||
Returns:
|
||||
Absolute path to generated HTML file.
|
||||
@@ -3171,7 +3178,9 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
),
|
||||
)
|
||||
structure = build_flow_structure(self)
|
||||
return render_interactive(structure, filename=filename, show=show)
|
||||
return render_interactive(
|
||||
structure, filename=filename, show=show, output_dir=output_dir
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _show_tracing_disabled_message() -> None:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Any, ClassVar
|
||||
import webbrowser
|
||||
|
||||
@@ -205,20 +204,24 @@ def render_interactive(
|
||||
dag: FlowStructure,
|
||||
filename: str = "flow_dag.html",
|
||||
show: bool = True,
|
||||
output_dir: str | None = None,
|
||||
) -> str:
|
||||
"""Create interactive HTML visualization of Flow structure.
|
||||
|
||||
Generates three output files in a temporary directory: HTML template,
|
||||
CSS stylesheet, and JavaScript. Optionally opens the visualization in
|
||||
default browser.
|
||||
Generates three output files: HTML template, CSS stylesheet, and
|
||||
JavaScript. Files are saved to the specified output directory, or the
|
||||
current working directory when *output_dir* is ``None``. Optionally
|
||||
opens the visualization in the default browser.
|
||||
|
||||
Args:
|
||||
dag: FlowStructure to visualize.
|
||||
filename: Output HTML filename (basename only, no path).
|
||||
show: Whether to open in browser.
|
||||
output_dir: Directory to save generated files. Defaults to the
|
||||
current working directory (``os.getcwd()``).
|
||||
|
||||
Returns:
|
||||
Absolute path to generated HTML file in temporary directory.
|
||||
Absolute path to generated HTML file.
|
||||
"""
|
||||
node_positions = calculate_node_positions(dag)
|
||||
|
||||
@@ -403,12 +406,13 @@ def render_interactive(
|
||||
extensions=[CSSExtension, JSExtension],
|
||||
)
|
||||
|
||||
temp_dir = Path(tempfile.mkdtemp(prefix="crewai_flow_"))
|
||||
output_path = temp_dir / Path(filename).name
|
||||
dest_dir = Path(output_dir) if output_dir else Path.cwd()
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = dest_dir / Path(filename).name
|
||||
css_filename = output_path.stem + "_style.css"
|
||||
css_output_path = temp_dir / css_filename
|
||||
css_output_path = dest_dir / css_filename
|
||||
js_filename = output_path.stem + "_script.js"
|
||||
js_output_path = temp_dir / js_filename
|
||||
js_output_path = dest_dir / js_filename
|
||||
|
||||
css_file = template_dir / "style.css"
|
||||
css_content = css_file.read_text(encoding="utf-8")
|
||||
|
||||
@@ -333,9 +333,9 @@ def test_visualization_plot_method():
|
||||
"""Test that flow.plot() method works."""
|
||||
flow = SimpleFlow()
|
||||
|
||||
html_file = flow.plot("test_plot.html", show=False)
|
||||
|
||||
assert os.path.exists(html_file)
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
html_file = flow.plot("test_plot.html", show=False, output_dir=tmp_dir)
|
||||
assert os.path.exists(html_file)
|
||||
|
||||
|
||||
def test_router_paths_to_string_conditions():
|
||||
@@ -667,4 +667,94 @@ def test_no_warning_for_properly_typed_router(caplog):
|
||||
# No warnings should be logged
|
||||
warning_messages = [r.message for r in caplog.records if r.levelno >= logging.WARNING]
|
||||
assert not any("Could not determine return paths" in msg for msg in warning_messages)
|
||||
assert not any("Found listeners waiting for triggers" in msg for msg in warning_messages)
|
||||
assert not any("Found listeners waiting for triggers" in msg for msg in warning_messages)
|
||||
|
||||
|
||||
def test_plot_saves_to_current_working_directory():
|
||||
"""Test that plot() saves the HTML file to the current working directory by default.
|
||||
|
||||
Regression test for https://github.com/crewAIInc/crewAI/issues/4991
|
||||
"""
|
||||
flow = SimpleFlow()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
original_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmp_dir)
|
||||
html_file = flow.plot("test_cwd_plot.html", show=False)
|
||||
|
||||
# The returned path must live inside the CWD, not a hidden temp dir
|
||||
assert Path(html_file).parent == Path(tmp_dir)
|
||||
assert os.path.exists(html_file)
|
||||
assert html_file == str(Path(tmp_dir) / "test_cwd_plot.html")
|
||||
finally:
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
def test_plot_saves_to_explicit_output_dir():
|
||||
"""Test that plot() saves files to a user-specified output directory."""
|
||||
flow = SimpleFlow()
|
||||
|
||||
with tempfile.TemporaryDirectory() as output_dir:
|
||||
html_file = flow.plot(
|
||||
"custom_output.html", show=False, output_dir=output_dir
|
||||
)
|
||||
|
||||
assert Path(html_file).parent == Path(output_dir)
|
||||
assert os.path.exists(html_file)
|
||||
|
||||
# CSS and JS companion files should also be in the same directory
|
||||
html_path = Path(html_file)
|
||||
css_file = html_path.parent / f"{html_path.stem}_style.css"
|
||||
js_file = html_path.parent / f"{html_path.stem}_script.js"
|
||||
assert css_file.exists()
|
||||
assert js_file.exists()
|
||||
|
||||
|
||||
def test_render_interactive_saves_to_cwd_by_default():
|
||||
"""Test that render_interactive() writes to CWD when output_dir is None.
|
||||
|
||||
Regression test for https://github.com/crewAIInc/crewAI/issues/4991
|
||||
"""
|
||||
flow = SimpleFlow()
|
||||
structure = build_flow_structure(flow)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
original_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmp_dir)
|
||||
html_file = visualize_flow_structure(
|
||||
structure, "cwd_test.html", show=False
|
||||
)
|
||||
|
||||
assert Path(html_file).parent == Path(tmp_dir)
|
||||
assert os.path.exists(html_file)
|
||||
finally:
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
def test_render_interactive_saves_to_specified_output_dir():
|
||||
"""Test that render_interactive() writes to the specified output_dir."""
|
||||
flow = SimpleFlow()
|
||||
structure = build_flow_structure(flow)
|
||||
|
||||
with tempfile.TemporaryDirectory() as output_dir:
|
||||
html_file = visualize_flow_structure(
|
||||
structure, "output_dir_test.html", show=False, output_dir=output_dir
|
||||
)
|
||||
|
||||
assert Path(html_file).parent == Path(output_dir)
|
||||
assert os.path.exists(html_file)
|
||||
|
||||
with open(html_file, "r", encoding="utf-8") as f:
|
||||
html_content = f.read()
|
||||
assert "<!DOCTYPE html>" in html_content
|
||||
|
||||
|
||||
def test_plot_returned_path_is_absolute():
|
||||
"""Test that the path returned by plot() is always absolute."""
|
||||
flow = SimpleFlow()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
html_file = flow.plot("abs_path_test.html", show=False, output_dir=tmp_dir)
|
||||
assert os.path.isabs(html_file)
|
||||
|
||||
Reference in New Issue
Block a user