Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,11 +13,9 @@ from networkx.drawing.nx_pydot import graphviz_layout
|
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
|
| 15 |
def get_wkhtmltopdf_path():
|
| 16 |
-
# Try using shutil.which first
|
| 17 |
path = shutil.which("wkhtmltopdf")
|
| 18 |
if path:
|
| 19 |
return path
|
| 20 |
-
# Then check common locations
|
| 21 |
for candidate in ["/usr/local/bin/wkhtmltopdf", "/usr/bin/wkhtmltopdf"]:
|
| 22 |
if os.path.exists(candidate):
|
| 23 |
return candidate
|
|
@@ -26,17 +24,15 @@ def get_wkhtmltopdf_path():
|
|
| 26 |
def process_uploaded_file(file):
|
| 27 |
"""
|
| 28 |
Process the file input from Gradio.
|
| 29 |
-
If file is a dict
|
| 30 |
-
|
| 31 |
-
- Otherwise, if "name" exists and points to a file, use it.
|
| 32 |
-
If file is a string, check that it's not a directory.
|
| 33 |
"""
|
|
|
|
| 34 |
if isinstance(file, dict):
|
| 35 |
-
#
|
| 36 |
if "data" in file and file["data"]:
|
| 37 |
temp_path = "/tmp/uploaded.csv"
|
| 38 |
try:
|
| 39 |
-
# Write as binary if data is bytes; otherwise, write as text.
|
| 40 |
mode = "wb" if isinstance(file["data"], bytes) else "w"
|
| 41 |
with open(temp_path, mode) as f:
|
| 42 |
f.write(file["data"])
|
|
@@ -45,11 +41,14 @@ def process_uploaded_file(file):
|
|
| 45 |
except Exception as e:
|
| 46 |
logging.error(f"Error writing uploaded file data: {e}")
|
| 47 |
return None
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
else:
|
| 52 |
-
logging.error("
|
| 53 |
return None
|
| 54 |
else:
|
| 55 |
# If file is a string, ensure it is not a directory.
|
|
@@ -59,16 +58,23 @@ def process_uploaded_file(file):
|
|
| 59 |
return file
|
| 60 |
|
| 61 |
def generate_chart(file, title):
|
|
|
|
| 62 |
if file is None:
|
| 63 |
return None, "Please upload a CSV file."
|
| 64 |
|
| 65 |
file_path = process_uploaded_file(file)
|
| 66 |
if file_path is None:
|
| 67 |
-
return None, "Error processing uploaded file. Please
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
try:
|
| 70 |
df = pd.read_csv(file_path)
|
| 71 |
except Exception as e:
|
|
|
|
| 72 |
return None, f"Error reading CSV: {e}"
|
| 73 |
|
| 74 |
# Validate required columns
|
|
@@ -76,7 +82,7 @@ def generate_chart(file, title):
|
|
| 76 |
if not expected_columns.issubset(set(df.columns)):
|
| 77 |
return None, "CSV must contain Name, Role, and Reporting To columns."
|
| 78 |
|
| 79 |
-
# Build
|
| 80 |
G = nx.DiGraph()
|
| 81 |
for _, row in df.iterrows():
|
| 82 |
node_label = f"{row['Name']}\n({row['Role']})"
|
|
@@ -94,34 +100,30 @@ def generate_chart(file, title):
|
|
| 94 |
if len(G.nodes) == 0:
|
| 95 |
return None, "No nodes to display in the chart."
|
| 96 |
|
| 97 |
-
#
|
| 98 |
if shutil.which("dot") is not None:
|
| 99 |
try:
|
| 100 |
logging.info("Using dot for layout.")
|
| 101 |
pos = graphviz_layout(G, prog="dot")
|
| 102 |
except Exception as e:
|
| 103 |
logging.error(f"Error using dot for layout: {e}")
|
| 104 |
-
logging.info("Falling back to spring_layout.")
|
| 105 |
pos = nx.spring_layout(G)
|
| 106 |
else:
|
| 107 |
-
logging.info("dot not found
|
| 108 |
pos = nx.spring_layout(G)
|
| 109 |
|
| 110 |
-
# Create the
|
| 111 |
plt.figure(figsize=(10, 6))
|
| 112 |
ax = plt.gca()
|
| 113 |
for node, (x, y) in pos.items():
|
| 114 |
-
# Draw shadow rectangle for a 3D effect
|
| 115 |
shadow = plt.Rectangle((x - 40 + 5, y - 20 - 5), 80, 40,
|
| 116 |
color='gray', alpha=0.3, zorder=1, ec="none")
|
| 117 |
ax.add_patch(shadow)
|
| 118 |
-
# Draw node rectangle with rounded edges
|
| 119 |
rect = plt.Rectangle((x - 40, y - 20), 80, 40, color='lightblue',
|
| 120 |
ec='black', lw=1.5, zorder=2, joinstyle="round")
|
| 121 |
ax.add_patch(rect)
|
| 122 |
plt.text(x, y, node, horizontalalignment='center', verticalalignment='center',
|
| 123 |
fontsize=10, zorder=3)
|
| 124 |
-
# Draw edges between nodes
|
| 125 |
for (u, v) in G.edges():
|
| 126 |
if u in pos and v in pos:
|
| 127 |
x1, y1 = pos[u]
|
|
@@ -129,18 +131,14 @@ def generate_chart(file, title):
|
|
| 129 |
plt.plot([x1, x2], [y1, y2], color="gray", zorder=1)
|
| 130 |
|
| 131 |
plt.axis('off')
|
| 132 |
-
|
| 133 |
-
# Save the chart as an image
|
| 134 |
chart_path = "/tmp/chart.png"
|
| 135 |
plt.savefig(chart_path, format="png", dpi=300, bbox_inches="tight")
|
| 136 |
plt.close()
|
| 137 |
|
| 138 |
-
# Convert the image to base64 for embedding in HTML (for PDF conversion)
|
| 139 |
with open(chart_path, "rb") as img_file:
|
| 140 |
encoded_string = base64.b64encode(img_file.read()).decode()
|
| 141 |
img_html = f"<img src='data:image/png;base64,{encoded_string}' style='width:100%;'/>"
|
| 142 |
|
| 143 |
-
# Build HTML content for PDF generation
|
| 144 |
html_content = f"""
|
| 145 |
<html>
|
| 146 |
<head>
|
|
@@ -158,7 +156,7 @@ def generate_chart(file, title):
|
|
| 158 |
wkhtmltopdf_path = get_wkhtmltopdf_path()
|
| 159 |
if wkhtmltopdf_path is None:
|
| 160 |
logging.error("wkhtmltopdf not found, skipping PDF generation.")
|
| 161 |
-
pdf_path = ""
|
| 162 |
else:
|
| 163 |
logging.info(f"wkhtmltopdf found at: {wkhtmltopdf_path}")
|
| 164 |
config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf_path)
|
|
|
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
|
| 15 |
def get_wkhtmltopdf_path():
|
|
|
|
| 16 |
path = shutil.which("wkhtmltopdf")
|
| 17 |
if path:
|
| 18 |
return path
|
|
|
|
| 19 |
for candidate in ["/usr/local/bin/wkhtmltopdf", "/usr/bin/wkhtmltopdf"]:
|
| 20 |
if os.path.exists(candidate):
|
| 21 |
return candidate
|
|
|
|
| 24 |
def process_uploaded_file(file):
|
| 25 |
"""
|
| 26 |
Process the file input from Gradio.
|
| 27 |
+
If 'file' is a dict, check if it contains valid file data.
|
| 28 |
+
Otherwise, if it's a string, ensure it's a file (not a directory).
|
|
|
|
|
|
|
| 29 |
"""
|
| 30 |
+
logging.info(f"Processing uploaded file: {file}")
|
| 31 |
if isinstance(file, dict):
|
| 32 |
+
# If "data" exists and is non-empty, write it to a temporary file.
|
| 33 |
if "data" in file and file["data"]:
|
| 34 |
temp_path = "/tmp/uploaded.csv"
|
| 35 |
try:
|
|
|
|
| 36 |
mode = "wb" if isinstance(file["data"], bytes) else "w"
|
| 37 |
with open(temp_path, mode) as f:
|
| 38 |
f.write(file["data"])
|
|
|
|
| 41 |
except Exception as e:
|
| 42 |
logging.error(f"Error writing uploaded file data: {e}")
|
| 43 |
return None
|
| 44 |
+
elif "name" in file and file["name"]:
|
| 45 |
+
if os.path.isfile(file["name"]):
|
| 46 |
+
return file["name"]
|
| 47 |
+
else:
|
| 48 |
+
logging.error("Uploaded file 'name' is not a file.")
|
| 49 |
+
return None
|
| 50 |
else:
|
| 51 |
+
logging.error("Uploaded file dict does not contain valid keys.")
|
| 52 |
return None
|
| 53 |
else:
|
| 54 |
# If file is a string, ensure it is not a directory.
|
|
|
|
| 58 |
return file
|
| 59 |
|
| 60 |
def generate_chart(file, title):
|
| 61 |
+
logging.info(f"generate_chart called with file: {file} and title: {title}")
|
| 62 |
if file is None:
|
| 63 |
return None, "Please upload a CSV file."
|
| 64 |
|
| 65 |
file_path = process_uploaded_file(file)
|
| 66 |
if file_path is None:
|
| 67 |
+
return None, "Error processing uploaded file. Please ensure you upload a valid CSV file."
|
| 68 |
+
|
| 69 |
+
# Check if the resulting file path is a directory (should never happen)
|
| 70 |
+
if os.path.isdir(file_path):
|
| 71 |
+
logging.error(f"Processed file path '{file_path}' is a directory.")
|
| 72 |
+
return None, "Uploaded file is a directory. Please upload a valid CSV file."
|
| 73 |
|
| 74 |
try:
|
| 75 |
df = pd.read_csv(file_path)
|
| 76 |
except Exception as e:
|
| 77 |
+
logging.error(f"Error reading CSV: {e}")
|
| 78 |
return None, f"Error reading CSV: {e}"
|
| 79 |
|
| 80 |
# Validate required columns
|
|
|
|
| 82 |
if not expected_columns.issubset(set(df.columns)):
|
| 83 |
return None, "CSV must contain Name, Role, and Reporting To columns."
|
| 84 |
|
| 85 |
+
# Build the directed graph
|
| 86 |
G = nx.DiGraph()
|
| 87 |
for _, row in df.iterrows():
|
| 88 |
node_label = f"{row['Name']}\n({row['Role']})"
|
|
|
|
| 100 |
if len(G.nodes) == 0:
|
| 101 |
return None, "No nodes to display in the chart."
|
| 102 |
|
| 103 |
+
# Use Graphviz 'dot' layout if available; otherwise, fall back to spring_layout.
|
| 104 |
if shutil.which("dot") is not None:
|
| 105 |
try:
|
| 106 |
logging.info("Using dot for layout.")
|
| 107 |
pos = graphviz_layout(G, prog="dot")
|
| 108 |
except Exception as e:
|
| 109 |
logging.error(f"Error using dot for layout: {e}")
|
|
|
|
| 110 |
pos = nx.spring_layout(G)
|
| 111 |
else:
|
| 112 |
+
logging.info("dot not found; using spring_layout.")
|
| 113 |
pos = nx.spring_layout(G)
|
| 114 |
|
| 115 |
+
# Create the chart plot
|
| 116 |
plt.figure(figsize=(10, 6))
|
| 117 |
ax = plt.gca()
|
| 118 |
for node, (x, y) in pos.items():
|
|
|
|
| 119 |
shadow = plt.Rectangle((x - 40 + 5, y - 20 - 5), 80, 40,
|
| 120 |
color='gray', alpha=0.3, zorder=1, ec="none")
|
| 121 |
ax.add_patch(shadow)
|
|
|
|
| 122 |
rect = plt.Rectangle((x - 40, y - 20), 80, 40, color='lightblue',
|
| 123 |
ec='black', lw=1.5, zorder=2, joinstyle="round")
|
| 124 |
ax.add_patch(rect)
|
| 125 |
plt.text(x, y, node, horizontalalignment='center', verticalalignment='center',
|
| 126 |
fontsize=10, zorder=3)
|
|
|
|
| 127 |
for (u, v) in G.edges():
|
| 128 |
if u in pos and v in pos:
|
| 129 |
x1, y1 = pos[u]
|
|
|
|
| 131 |
plt.plot([x1, x2], [y1, y2], color="gray", zorder=1)
|
| 132 |
|
| 133 |
plt.axis('off')
|
|
|
|
|
|
|
| 134 |
chart_path = "/tmp/chart.png"
|
| 135 |
plt.savefig(chart_path, format="png", dpi=300, bbox_inches="tight")
|
| 136 |
plt.close()
|
| 137 |
|
|
|
|
| 138 |
with open(chart_path, "rb") as img_file:
|
| 139 |
encoded_string = base64.b64encode(img_file.read()).decode()
|
| 140 |
img_html = f"<img src='data:image/png;base64,{encoded_string}' style='width:100%;'/>"
|
| 141 |
|
|
|
|
| 142 |
html_content = f"""
|
| 143 |
<html>
|
| 144 |
<head>
|
|
|
|
| 156 |
wkhtmltopdf_path = get_wkhtmltopdf_path()
|
| 157 |
if wkhtmltopdf_path is None:
|
| 158 |
logging.error("wkhtmltopdf not found, skipping PDF generation.")
|
| 159 |
+
pdf_path = ""
|
| 160 |
else:
|
| 161 |
logging.info(f"wkhtmltopdf found at: {wkhtmltopdf_path}")
|
| 162 |
config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf_path)
|