Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// * Imports *
import { useState, useCallback, useEffect, useRef, version } from 'react';

Check failure on line 2 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'version' is defined but never used. Allowed unused vars must match /^[A-Z_]/u

Check failure on line 2 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'version' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
import {
ReactFlowProvider,
useReactFlow,
Expand Down Expand Up @@ -90,7 +90,7 @@
}, []);

const onDragStart = (event, nodeType) => {
setType(nodeType);

Check failure on line 93 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'setType' is not defined

Check failure on line 93 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'setType' is not defined
event.dataTransfer.setData('text/plain', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
Expand All @@ -107,7 +107,7 @@
const [pythonCode, setPythonCode] = useState("# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n");

// State for URL sharing feedback
const [shareUrlFeedback, setShareUrlFeedback] = useState('');

Check failure on line 110 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'shareUrlFeedback' is assigned a value but never used. Allowed unused vars must match /^[A-Z_]/u

Check failure on line 110 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'shareUrlFeedback' is assigned a value but never used. Allowed unused vars must match /^[A-Z_]/u
const [showShareModal, setShowShareModal] = useState(false);
const [shareableURL, setShareableURL] = useState('');
const [urlMetadata, setUrlMetadata] = useState(null);
Expand Down Expand Up @@ -161,7 +161,7 @@
};

loadGraphFromURL();
}, []); // Empty dependency array means this runs once on mount

Check warning on line 164 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useEffect has missing dependencies: 'setEdges' and 'setNodes'. Either include them or remove the dependency array

Check warning on line 164 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useEffect has missing dependencies: 'setEdges' and 'setNodes'. Either include them or remove the dependency array

const [defaultValues, setDefaultValues] = useState({});
const [isEditingLabel, setIsEditingLabel] = useState(false);
Expand Down Expand Up @@ -366,7 +366,7 @@
setNodes((nds) => [...nds, newNode]);
setNodeCounter((count) => count + 1);
},
[screenToFlowPosition, type, nodeCounter, fetchDefaultValues, setDefaultValues, setNodes, setNodeCounter],

Check warning on line 369 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array

Check warning on line 369 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array
);

// Function to save a graph to computer with "Save As" dialog
Expand Down Expand Up @@ -791,7 +791,25 @@
body: JSON.stringify({ graph: graphData }),
});

const result = await response.json();
// Check if response is ok first
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

// Check if response has content
const responseText = await response.text();
if (!responseText.trim()) {
throw new Error('Server returned empty response');
}

// Try to parse JSON
let result;
try {
result = JSON.parse(responseText);
} catch (jsonError) {
console.error('Failed to parse JSON response:', responseText);
throw new Error(`Invalid JSON response: ${jsonError.message}`);
}

if (sseRef.current) { sseRef.current.close(); sseRef.current = null; }

Expand All @@ -805,8 +823,29 @@
alert(`Error running Pathsim simulation: ${result.error}`);
}
} catch (error) {
console.error('Error:', error);
alert(`Failed to run Pathsim simulation. Make sure the backend is running. : ${error.message}`);
console.error('Error details:', {
message: error.message,
stack: error.stack,
response: error.response || 'No response object'
});

if (sseRef.current) {
sseRef.current.close();
sseRef.current = null;
}

// Provide more specific error messages
let errorMessage = 'Failed to run Pathsim simulation. Make sure the backend is running.';

if (error.message.includes('JSON')) {
errorMessage = 'Server response was not valid JSON. This might be due to a server error or network issue.';
} else if (error.message.includes('HTTP')) {
errorMessage = `Server error: ${error.message}`;
} else if (error.message.includes('empty response')) {
errorMessage = 'Server returned empty response. The simulation might have failed silently.';
}

alert(`${errorMessage} : ${error.message}`);
}
};

Expand Down Expand Up @@ -1052,7 +1091,7 @@
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedEdge, selectedNode, copiedNode, duplicateNode, setCopyFeedback]);

Check warning on line 1094 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

Check warning on line 1094 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

return (
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
Expand Down
70 changes: 60 additions & 10 deletions src/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

# imports for logging progress
from flask import Response, stream_with_context
import time
import logging
from queue import Queue, Empty

Expand Down Expand Up @@ -94,9 +93,18 @@ def logs_stream():
def gen():
yield "retry: 500\n\n"
while True:
line = log_queue.get()
for chunk in line.replace("\r", "\n").splitlines():
yield f"data: {chunk}\n\n"
try:
# Use a timeout to prevent indefinite blocking
line = log_queue.get(timeout=30)
for chunk in line.replace("\r", "\n").splitlines():
yield f"data: {chunk}\n\n"
except Empty:
# Send a heartbeat to keep connection alive
yield "data: \n\n"
except Exception as e:
# Log the error and break the loop to close the connection
yield f"data: Error in log stream: {str(e)}\n\n"
break

return Response(gen(), mimetype="text/event-stream")

Expand Down Expand Up @@ -358,6 +366,19 @@ def run_pathsim():
# Share x only if there are only scopes or only spectra
shared_x = len(scopes) * len(spectra) == 0
n_rows = len(scopes) + len(spectra)

if n_rows == 0:
# No scopes or spectra to plot
return jsonify(
{
"success": True,
"plot": "{}",
"html": "<p>No scopes or spectra to display</p>",
"csv_data": csv_payload,
"message": "Pathsim simulation completed successfully",
}
)

absolute_vertical_spacing = 0.05
relative_vertical_spacing = absolute_vertical_spacing / n_rows
fig = make_subplots(
Expand All @@ -371,27 +392,27 @@ def run_pathsim():

# make scope plots
for i, scope in enumerate(scopes):
time, data = scope.read()
sim_time, data = scope.read()

for p, d in enumerate(data):
lb = scope.labels[p] if p < len(scope.labels) else f"port {p}"
if isinstance(scope, Spectrum):
d = abs(d)
fig.add_trace(
go.Scatter(x=time, y=d, mode="lines", name=lb), row=i + 1, col=1
go.Scatter(x=sim_time, y=d, mode="lines", name=lb), row=i + 1, col=1
)

fig.update_xaxes(title_text="Time", row=len(scopes), col=1)

# make spectrum plots
for i, spec in enumerate(spectra):
time, data = spec.read()
freq, data = spec.read()

for p, d in enumerate(data):
lb = spec.labels[p] if p < len(spec.labels) else f"port {p}"
d = abs(d)
fig.add_trace(
go.Scatter(x=time, y=d, mode="lines", name=lb),
go.Scatter(x=freq, y=d, mode="lines", name=lb),
row=len(scopes) + i + 1,
col=1,
)
Expand All @@ -402,8 +423,13 @@ def run_pathsim():
)

# Convert plot to JSON
plot_data = plotly_json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
plot_html = fig.to_html()
try:
plot_data = plotly_json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
plot_html = fig.to_html()
except Exception as plot_error:
return jsonify(
{"success": False, "error": f"Plot generation error: {str(plot_error)}"}
), 500

return jsonify(
{
Expand All @@ -416,6 +442,11 @@ def run_pathsim():
)

except Exception as e:
# Log the full error for debugging
import traceback

error_details = traceback.format_exc()
print(f"Error in run_pathsim: {error_details}")
return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500


Expand Down Expand Up @@ -503,6 +534,25 @@ def catch_all(path):
return jsonify({"error": "Route not found"}), 404


# Global error handler to ensure all errors return JSON
@app.errorhandler(Exception)
def handle_exception(e):
"""Global exception handler to ensure JSON responses."""
import traceback

error_details = traceback.format_exc()
print(f"Unhandled exception: {error_details}")

# For HTTP exceptions, return the original response
if hasattr(e, "code"):
return jsonify(
{"success": False, "error": f"HTTP {e.code}: {str(e)}"}
), getattr(e, "code", 500)

# For all other exceptions, return a generic JSON error
return jsonify({"success": False, "error": f"Internal server error: {str(e)}"}), 500


if __name__ == "__main__":
port = int(os.getenv("PORT", 8000))
app.run(host="0.0.0.0", port=port, debug=os.getenv("FLASK_ENV") != "production")
Loading