diff --git a/src/crewai/flow/fonts/arial_bold.ttf b/src/crewai/flow/assets/arial_bold.ttf
similarity index 100%
rename from src/crewai/flow/fonts/arial_bold.ttf
rename to src/crewai/flow/assets/arial_bold.ttf
diff --git a/src/crewai/flow/assets/crewai_flow_visual_template.html b/src/crewai/flow/assets/crewai_flow_visual_template.html
new file mode 100644
index 000000000..f175ef1a7
--- /dev/null
+++ b/src/crewai/flow/assets/crewai_flow_visual_template.html
@@ -0,0 +1,93 @@
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+

+
+
+
+ {{ network_content }}
+
+
diff --git a/src/crewai/flow/assets/crewai_logo.svg b/src/crewai/flow/assets/crewai_logo.svg
new file mode 100644
index 000000000..1668a48e5
--- /dev/null
+++ b/src/crewai/flow/assets/crewai_logo.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/crewai/flow/flow_visualizer.py b/src/crewai/flow/flow_visualizer.py
index d473a34bc..ef599c436 100644
--- a/src/crewai/flow/flow_visualizer.py
+++ b/src/crewai/flow/flow_visualizer.py
@@ -1,5 +1,6 @@
-import shutil
-import warnings
+import base64
+import os
+import re
from abc import ABC, abstractmethod
from pyvis.network import Network
@@ -16,92 +17,29 @@ class FlowVisualizer(ABC):
"edge": "#666666",
"text": "#FFFFFF",
}
+ self.node_styles = {
+ "start": {
+ "color": self.colors["start"],
+ "shape": "box",
+ "font": {"color": self.colors["text"]},
+ },
+ "method": {
+ "color": self.colors["method"],
+ "shape": "box",
+ "font": {"color": self.colors["text"]},
+ },
+ "router": {
+ "color": self.colors["router"],
+ "shape": "box",
+ "font": {"color": self.colors["text"]},
+ },
+ }
@abstractmethod
def visualize(self, filename):
pass
-class GraphvizVisualizer(FlowVisualizer):
- def visualize(self, filename):
- import graphviz
-
- dot = graphviz.Digraph(comment="Flow Graph", engine="dot")
- dot.attr(rankdir="TB", size="20,20", splines="curved")
- dot.attr(bgcolor=self.colors["bg"])
-
- # Add nodes
- for method_name, method in self.flow._methods.items():
- if (
- hasattr(method, "__is_start_method__")
- or method_name in self.flow._listeners
- or method_name in self.flow._routers.values()
- ):
- shape = "rectangle"
- style = "filled,rounded"
- fillcolor = (
- self.colors["start"]
- if hasattr(method, "__is_start_method__")
- else self.colors["method"]
- )
-
- dot.node(
- method_name,
- method_name,
- shape=shape,
- style=style,
- fillcolor=fillcolor,
- fontcolor=self.colors["text"],
- penwidth="2",
- )
-
- # Add edges and routers
- for method_name, method in self.flow._methods.items():
- if method_name in self.flow._listeners:
- condition_type, trigger_methods = self.flow._listeners[method_name]
- for trigger in trigger_methods:
- style = "dashed" if condition_type == "AND" else "solid"
- dot.edge(
- trigger,
- method_name,
- color=self.colors["edge"],
- style=style,
- penwidth="2",
- )
-
- if method_name in self.flow._routers.values():
- for trigger, router in self.flow._routers.items():
- if router == method_name:
- subgraph_name = f"cluster_{method_name}"
- subgraph = graphviz.Digraph(name=subgraph_name)
- subgraph.attr(
- label="",
- style="filled,rounded",
- color=self.colors["router_outline"],
- fillcolor=self.colors["method"],
- penwidth="3",
- )
- label = f"{method_name}\\n\\nPossible outcomes:\\n• Success\\n• Failure"
- subgraph.node(
- method_name,
- label,
- shape="plaintext",
- fontcolor=self.colors["text"],
- )
- dot.subgraph(subgraph)
- dot.edge(
- trigger,
- method_name,
- color=self.colors["edge"],
- style="solid",
- penwidth="2",
- lhead=subgraph_name,
- )
-
- dot.render(filename, format="png", cleanup=True, view=True)
- print(f"Graph saved as {filename}.png")
-
-
class PyvisFlowVisualizer(FlowVisualizer):
def visualize(self, filename):
net = Network(
@@ -112,25 +50,6 @@ class PyvisFlowVisualizer(FlowVisualizer):
layout=None,
)
- # Define custom node styles
- node_styles = {
- "start": {
- "color": self.colors.get("start", "#FF5A50"),
- "shape": "box",
- "font": {"color": self.colors.get("text", "#FFFFFF")},
- },
- "method": {
- "color": self.colors.get("method", "#333333"),
- "shape": "box",
- "font": {"color": self.colors.get("text", "#FFFFFF")},
- },
- "router": {
- "color": self.colors.get("router", "#FF8C00"),
- "shape": "box",
- "font": {"color": self.colors.get("text", "#FFFFFF")},
- },
- }
-
# Calculate levels for nodes
node_levels = self._calculate_node_levels()
@@ -150,11 +69,11 @@ class PyvisFlowVisualizer(FlowVisualizer):
y = level * y_spacing # Use level directly for y position
method = self.flow._methods.get(method_name)
if hasattr(method, "__is_start_method__"):
- node_style = node_styles["start"]
+ node_style = self.node_styles["start"]
elif method_name in self.flow._routers.values():
- node_style = node_styles["router"]
+ node_style = self.node_styles["router"]
else:
- node_style = node_styles["method"]
+ node_style = self.node_styles["method"]
net.add_node(
method_name,
@@ -185,23 +104,101 @@ class PyvisFlowVisualizer(FlowVisualizer):
# Set options for curved edges and disable physics
net.set_options(
"""
- var options = {
- "physics": {
- "enabled": false
- },
- "edges": {
- "smooth": {
- "enabled": true,
- "type": "cubicBezier",
- "roundness": 0.5
- }
- }
- }
- """
+ var options = {
+ "physics": {
+ "enabled": false
+ },
+ "edges": {
+ "smooth": {
+ "enabled": true,
+ "type": "cubicBezier",
+ "roundness": 0.5
+ }
+ }
+ }
+ """
)
- # Generate and save the graph
- net.write_html(f"{filename}.html")
+ network_html = net.generate_html()
+
+ # Extract just the body content from the generated HTML
+ match = re.search("(.*?)