Skip to content

Commit a5cf5b4

Browse files
committed
refractored the code
1 parent 1839e90 commit a5cf5b4

File tree

16 files changed

+375
-282
lines changed

16 files changed

+375
-282
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ init: init-venv ## Init virtual environment
4747
@./venv/bin/python3 -m pip install -r requirements.dev.txt
4848
@./venv/bin/python3 -m pre_commit install --install-hooks --overwrite
4949
@echo "use 'source venv/bin/activate' to activate venv "
50+
@./venv/bin/python3 -m pip install -e .
5051

5152
format: ## Run formatter on source code
5253
@./venv/bin/python3 -m black --config=pyproject.toml .
@@ -68,4 +69,4 @@ clean: clean-lite ## Remove virtual environment, downloaded models, etc
6869
@echo "run 'make init'"
6970

7071
run: ## Run the application
71-
@./venv/bin/python3 main.py
72+
@./venv/bin/python3 beamforge/app.py

beamforge/__init__.py

Whitespace-only changes.

beamforge/app.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# third party libraries
2+
import dash
3+
import dash_cytoscape as cyto
4+
5+
from beamforge.callbacks.graph_callbacks import register_graph_callbacks
6+
from beamforge.callbacks.yaml_callbacks import register_yaml_callbacks
7+
from beamforge.layouts.main_layout import create_layout
8+
9+
# Register the dagre layout
10+
cyto.load_extra_layouts()
11+
12+
# Initialize the Dash app
13+
app = dash.Dash(__name__)
14+
15+
# Set the layout
16+
app.layout = create_layout()
17+
18+
# Register callbacks
19+
register_graph_callbacks(app)
20+
register_yaml_callbacks(app)
21+
22+
if __name__ == "__main__":
23+
app.run_server(debug=True)
File renamed without changes.

beamforge/callbacks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty file to make the directory a Python package
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# standard libraries
2+
import base64
3+
import io
4+
5+
# third party libraries
6+
from dash import Input, Output, State, html
7+
from utils.yaml_parser import parse_beam_yaml
8+
9+
10+
def register_graph_callbacks(app):
11+
# Clientside callback for fitting the graph
12+
app.clientside_callback(
13+
"""
14+
function(elements) {
15+
if (elements && elements.length > 0) {
16+
const cy = document.getElementById('network-graph')._cyreg.cy;
17+
setTimeout(() => cy.fit(), 100);
18+
}
19+
return elements;
20+
}
21+
""",
22+
Output("network-graph", "elements"),
23+
Input("network-graph", "elements"),
24+
)
25+
26+
@app.callback(
27+
Output("network-graph", "elements", allow_duplicate=True),
28+
Input("upload-data", "contents"),
29+
State("upload-data", "filename"),
30+
prevent_initial_call=True,
31+
)
32+
def update_graph(contents, filename):
33+
if contents is None:
34+
return []
35+
36+
content_type, content_string = contents.split(",")
37+
decoded = base64.b64decode(content_string)
38+
39+
try:
40+
# Parse YAML and create NetworkX graph
41+
G = parse_beam_yaml(io.StringIO(decoded.decode("utf-8")))
42+
43+
# Convert NetworkX graph to Cytoscape format
44+
elements = []
45+
46+
# Add nodes
47+
for node in G.nodes(data=True):
48+
node_id = node[0]
49+
node_data = node[1]
50+
elements.append(
51+
{
52+
"data": {
53+
"id": node_id,
54+
"type": node_data.get("type", "Unknown"),
55+
"config": node_data.get("config", {}),
56+
}
57+
}
58+
)
59+
60+
# Add edges
61+
for edge in G.edges():
62+
elements.append({"data": {"source": edge[0], "target": edge[1]}})
63+
64+
return elements
65+
except Exception as e:
66+
print(e)
67+
return []
68+
69+
@app.callback(Output("node-data", "children"), Input("network-graph", "tapNodeData"))
70+
def display_node_data(node_data):
71+
if not node_data:
72+
return "Click a node to see its details"
73+
74+
# Create a formatted display of node data
75+
details = [
76+
html.H4(f"Transform: {node_data['id']}"),
77+
html.P(f"Type: {node_data['type']}"),
78+
html.H5("Configuration:"),
79+
html.Pre(
80+
str(node_data["config"]),
81+
style={
82+
"whiteSpace": "pre-wrap",
83+
"wordBreak": "break-all",
84+
"backgroundColor": "#f5f5f5",
85+
"padding": "10px",
86+
"borderRadius": "4px",
87+
},
88+
),
89+
]
90+
91+
return html.Div(details)

beamforge/callbacks/yaml_callbacks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# standard libraries
2+
import base64
3+
4+
# third party libraries
5+
import yaml
6+
from dash import Input, Output
7+
8+
9+
def register_yaml_callbacks(app):
10+
@app.callback(
11+
Output("yaml-content", "children"),
12+
Input("upload-data", "contents"),
13+
)
14+
def update_yaml_content(contents):
15+
if contents is None:
16+
return "YAML content will appear here..."
17+
18+
content_type, content_string = contents.split(",")
19+
decoded = base64.b64decode(content_string)
20+
21+
try:
22+
yaml_dict = yaml.safe_load(decoded)
23+
formatted_yaml = yaml.dump(yaml_dict, default_flow_style=False, sort_keys=False)
24+
return formatted_yaml
25+
except Exception as e:
26+
return f"Error processing YAML file: {str(e)}"

beamforge/layouts/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty file to make the directory a Python package

beamforge/layouts/left_panel.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# third party libraries
2+
import dash_resizable_panels as drp
3+
from dash import dcc, html
4+
5+
6+
def create_left_panel():
7+
return drp.Panel(
8+
html.Div(
9+
[
10+
html.H3("Upload Beam YAML"),
11+
dcc.Upload(
12+
id="upload-data",
13+
children=html.Div(["Drag and Drop or ", html.A("Select Beam YAML File")]),
14+
style={
15+
"width": "80%",
16+
"height": "60px",
17+
"lineHeight": "60px",
18+
"borderWidth": "1px",
19+
"borderStyle": "dashed",
20+
"borderRadius": "5px",
21+
"textAlign": "center",
22+
"margin": "10px",
23+
},
24+
multiple=False,
25+
accept=".yaml,.yml",
26+
),
27+
html.Div(
28+
[
29+
html.Pre(
30+
id="yaml-content",
31+
style={
32+
"width": "95%",
33+
"margin": "10px",
34+
"fontFamily": "monospace",
35+
"whiteSpace": "pre-wrap",
36+
"wordWrap": "break-word",
37+
"backgroundColor": "#f5f5f5",
38+
"padding": "10px",
39+
"border": "1px solid #ddd",
40+
"borderRadius": "4px",
41+
"overflow": "auto",
42+
"maxHeight": "calc(100vh - 150px)",
43+
},
44+
),
45+
],
46+
style={"height": "calc(100% - 100px)", "overflow": "auto"},
47+
),
48+
]
49+
),
50+
defaultSizePercentage=20,
51+
)

beamforge/layouts/main_layout.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# third party libraries
2+
import dash_resizable_panels as drp
3+
from dash import html
4+
5+
from beamforge.layouts.left_panel import create_left_panel
6+
from beamforge.layouts.middle_panel import create_middle_panel
7+
from beamforge.layouts.right_panel import create_right_panel
8+
9+
10+
def create_layout():
11+
return html.Div(
12+
[
13+
drp.PanelGroup(
14+
[
15+
create_left_panel(),
16+
drp.PanelResizeHandle(className="panel-resize-handle"),
17+
create_middle_panel(),
18+
drp.PanelResizeHandle(className="panel-resize-handle"),
19+
create_right_panel(),
20+
],
21+
direction="horizontal",
22+
),
23+
html.Div(id="reset-trigger", style={"display": "none"}),
24+
],
25+
style={"height": "100vh"},
26+
)

beamforge/layouts/middle_panel.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# third party libraries
2+
import dash_cytoscape as cyto
3+
import dash_resizable_panels as drp
4+
from dash import html
5+
6+
7+
def get_stylesheet():
8+
return [
9+
{
10+
"selector": "node",
11+
"style": {
12+
"shape": "rectangle",
13+
"content": "data(id)",
14+
"text-valign": "center",
15+
"text-halign": "center",
16+
"width": "150px",
17+
"height": "40px",
18+
"background-color": "#e3f2fd",
19+
"border-color": "#1976d2",
20+
"border-width": "2px",
21+
"padding": "8px",
22+
"font-family": "Roboto, Arial, sans-serif",
23+
"font-size": "12px",
24+
"font-weight": "500",
25+
"text-wrap": "wrap",
26+
"color": "#1565c0",
27+
},
28+
},
29+
{
30+
"selector": "edge",
31+
"style": {
32+
"curve-style": "bezier",
33+
"target-arrow-shape": "triangle",
34+
"line-color": "#90caf9",
35+
"target-arrow-color": "#90caf9",
36+
"width": 1.5,
37+
"arrow-scale": 1.2,
38+
},
39+
},
40+
{
41+
"selector": "node:selected",
42+
"style": {
43+
"background-color": "#bbdefb",
44+
"border-color": "#0d47a1",
45+
"border-width": "3px",
46+
},
47+
},
48+
]
49+
50+
51+
def create_middle_panel():
52+
return drp.Panel(
53+
html.Div(
54+
[
55+
html.H3("Pipeline Graph"),
56+
cyto.Cytoscape(
57+
id="network-graph",
58+
layout={"name": "dagre", "rankDir": "TB", "rankSep": 30, "nodeSep": 50, "padding": 10},
59+
style={
60+
"width": "100%",
61+
"height": "calc(100vh - 100px)",
62+
"position": "absolute",
63+
},
64+
stylesheet=get_stylesheet(),
65+
elements=[],
66+
),
67+
],
68+
style={
69+
"height": "100%",
70+
"position": "relative",
71+
"display": "flex",
72+
"flexDirection": "column",
73+
},
74+
),
75+
defaultSizePercentage=60,
76+
)

beamforge/layouts/right_panel.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# third party libraries
2+
import dash_resizable_panels as drp
3+
from dash import html
4+
5+
6+
def create_right_panel():
7+
return drp.Panel(
8+
html.Div(
9+
[
10+
html.H3("Transform Details"),
11+
html.Div(
12+
id="node-info",
13+
children=[html.P("Click a transform to see its details"), html.Div(id="node-data")],
14+
),
15+
]
16+
),
17+
defaultSizePercentage=20,
18+
)

beamforge/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty file to make the directory a Python package

beamforge/utils/yaml_parser.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# third party libraries
2+
import networkx as nx
3+
import yaml
4+
5+
6+
def parse_beam_yaml(yaml_content):
7+
"""Parse Beam YAML and create a NetworkX graph."""
8+
G = nx.DiGraph()
9+
data = yaml.safe_load(yaml_content)
10+
11+
if "pipeline" not in data:
12+
raise ValueError("No pipeline section found in YAML")
13+
14+
pipeline = data["pipeline"]
15+
transforms = pipeline.get("transforms", [])
16+
pipeline_type = pipeline.get("type", None)
17+
18+
if pipeline_type == "chain" or pipeline_type is None:
19+
prev_node = None
20+
for idx, transform in enumerate(transforms):
21+
node_id = transform.get("name", f"transform_{idx}")
22+
transform_type = transform.get("type", "Unknown")
23+
G.add_node(node_id, type=transform_type, config=transform.get("config", {}))
24+
if prev_node is not None:
25+
G.add_edge(prev_node, node_id)
26+
prev_node = node_id
27+
28+
return G

0 commit comments

Comments
 (0)