Skip to content

Commit 09a50ba

Browse files
committed
UPDATE Flask backend for React integration and enhance educational templates
1 parent f1c07d8 commit 09a50ba

12 files changed

Lines changed: 175 additions & 332 deletions

app.py

Lines changed: 58 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ def configure_logging():
8383
# Create application logger
8484
logger = configure_logging()
8585

86-
# Initialize Flask application
87-
app = Flask(__name__, static_folder='frontend/build')
86+
# Initialize Flask application (serve React build in production)
87+
app = Flask(__name__, static_folder='frontend/build', static_url_path='/')
8888
CORS(app, resources={
8989
r"/api/*": {
90-
"origins": ["http://localhost:3000"],
90+
"origins": ["http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5000", "http://127.0.0.1:5000"],
9191
"methods": ["GET", "POST", "OPTIONS"],
9292
"allow_headers": ["Content-Type"]
9393
}
@@ -398,7 +398,14 @@ def validate_parameters(plugin, params):
398398
{'name': 'noise', 'type': 'float', 'default': 0.0, 'description': 'Noise level (0.0 - 0.2)',"min": 0, "max": 0.2},
399399
{'name': 'dimension', 'type': 'int', 'default': 4, 'description': 'Lattice dimension parameter',"min": 1, "max": 32}
400400
],
401-
'function': generate_quantum_fingerprint_cirq
401+
'function': generate_quantum_fingerprint_cirq,
402+
# Standardized runner to match other plugins and Socket.IO flow
403+
'run': lambda p: run_plugin(
404+
generate_quantum_fingerprint_cirq,
405+
_plugin_key="auth",
406+
data=p.get("username", "Bob"),
407+
num_qubits=p.get("dimension", 4)
408+
)
402409
},
403410

404411
"bb84": {
@@ -765,16 +772,8 @@ def get_educational_content(plugin_key):
765772
# --- Route handlers ---
766773
@app.route("/")
767774
def index():
768-
"""Render the homepage with a list of available plugins."""
769-
# Group plugins by category for better organization
770-
categories = {}
771-
for key, plugin in PLUGINS.items():
772-
category = plugin.get("category", "other")
773-
if category not in categories:
774-
categories[category] = []
775-
categories[category].append({"key": key, **plugin})
776-
777-
return render_template("index.html", plugins=PLUGINS, categories=categories)
775+
# Serve React index.html
776+
return send_from_directory(app.static_folder, 'index.html')
778777

779778
@app.route('/sitemap.xml')
780779
def sitemap():
@@ -839,21 +838,8 @@ def sitemap():
839838

840839
@app.route("/sitemap")
841840
def html_sitemap():
842-
"""HTML sitemap for users and SEO."""
843-
# Group plugins by category
844-
categories = {}
845-
for key, plugin in PLUGINS.items():
846-
category = plugin.get("category", "other")
847-
if category not in categories:
848-
categories[category] = []
849-
categories[category].append({"key": key, **plugin})
850-
851-
return render_template(
852-
"sitemap.html",
853-
categories=categories,
854-
meta_title="Site Map | Quantum Field Kit",
855-
meta_description="Comprehensive site map of Quantum Field Kit's quantum computing simulations, educational resources, and tools."
856-
)
841+
# Serve SPA index for HTML sitemap requests
842+
return send_from_directory(app.static_folder, 'index.html')
857843

858844
@app.route('/robots.txt')
859845
def robots():
@@ -876,51 +862,11 @@ def robots():
876862

877863
@app.route("/category/<category>")
878864
def category_view(category):
879-
"""Display all plugins in a specific category with SEO optimizations."""
880-
# Filter plugins by category
881-
plugins_in_category = {k: v for k, v in PLUGINS.items() if v.get("category", "other") == category}
882-
883-
if not plugins_in_category:
884-
abort(404)
885-
886-
# Properly formatted category name for display
887-
display_category = category.replace('-', ' ').title()
888-
889-
return render_template(
890-
"category.html",
891-
category=category,
892-
display_category=display_category,
893-
plugins=plugins_in_category,
894-
meta_description=f"Explore {display_category} quantum computing simulations including {', '.join([p['name'] for p in list(plugins_in_category.values())[:3]])} and more.",
895-
meta_title=f"{display_category} Quantum Computing Simulations | Quantum Field Kit"
896-
)
865+
return send_from_directory(app.static_folder, 'index.html')
897866

898867
@app.route("/glossary")
899868
def glossary():
900-
"""Quantum computing glossary page."""
901-
import datetime
902-
current_year = datetime.datetime.now().year
903-
# Load terms from JSON file
904-
terms_file = os.path.join(app.static_folder, 'data', 'glossary_terms.json')
905-
906-
try:
907-
with open(terms_file, 'r') as f:
908-
terms = json.load(f)
909-
except (FileNotFoundError, json.JSONDecodeError) as e:
910-
app.logger.error(f"Error loading glossary terms: {e}")
911-
# Fallback to minimal terms if file can't be loaded
912-
terms = [
913-
{"term": "Qubit", "definition": "The fundamental unit of quantum information."},
914-
{"term": "Superposition", "definition": "A quantum property allowing particles to exist in multiple states."}
915-
]
916-
917-
return render_template(
918-
"glossary.html",
919-
terms=terms,
920-
current_year=current_year,
921-
meta_title="Quantum Computing Glossary | Quantum Field Kit",
922-
meta_description="Comprehensive glossary of quantum computing terms, concepts, and principles explained in simple language."
923-
)
869+
return send_from_directory(app.static_folder, 'index.html')
924870

925871
@app.after_request
926872
def add_cache_headers(response):
@@ -940,103 +886,26 @@ def add_cache_headers(response):
940886

941887
@app.route("/plugin/<plugin_key>", methods=["GET", "POST"])
942888
def plugin_view(plugin_key):
943-
"""Handle individual plugin pages and simulation requests."""
944-
if plugin_key not in PLUGINS:
945-
abort(404)
946-
947-
plugin = PLUGINS[plugin_key]
948-
result = None
949-
950-
# Get mini explanation (already working)
951-
mini_explanation = get_mini_explanation(plugin_key)
952-
953-
# Get educational content directly
954-
educational_content = get_educational_content(plugin_key)
955-
956-
# Process form submission...
957-
result = None
958889
if request.method == 'POST':
890+
# Support legacy AJAX POSTs from static JS (if any)
891+
if plugin_key not in PLUGINS:
892+
return jsonify({"error": "Plugin not found"}), 404
893+
plugin = PLUGINS[plugin_key]
959894
try:
960-
# Get plugin function
961-
plugin_function = plugin.get('function')
962-
if not plugin_function:
963-
raise ValueError(f"Plugin function not found for {plugin_key}")
964-
965-
# Special handling for auth plugin
966-
if plugin_key == 'auth':
967-
username = request.form.get('username', 'user123')
968-
noise = float(request.form.get('noise', 0.0))
969-
dimension = int(request.form.get('dimension', 4))
970-
971-
# Call the function with updated parameters for the lattice-based implementation
972-
output = plugin_function(username, dimension)
973-
974-
# Process log for display
975-
log = output.get('log', '')
976-
977-
result = {
978-
'output': output,
979-
'log': log
980-
}
981-
else:
982-
# Process parameters from form
983-
params = {}
984-
for param in plugin.get('parameters', []):
985-
param_name = param['name']
986-
param_type = param['type']
987-
988-
# Get form value
989-
form_value = request.form.get(param_name)
990-
if form_value is None:
991-
continue
992-
993-
# Convert value to appropriate type
994-
if param_type == 'int':
995-
params[param_name] = int(form_value)
996-
elif param_type == 'float':
997-
params[param_name] = float(form_value)
998-
else:
999-
params[param_name] = form_value
1000-
1001-
# Call the plugin function with parameters
1002-
output = plugin_function(**params)
1003-
1004-
# Process log for display
1005-
if isinstance(output, dict) and 'log' in output:
1006-
log = output.get('log', '')
1007-
else:
1008-
log = f"Execution complete. No detailed log available."
1009-
1010-
result = {
1011-
'output': output,
1012-
'log': log
1013-
}
1014-
1015-
# Return JSON response for AJAX requests
1016-
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
1017-
return jsonify(result)
1018-
895+
raw_params = {}
896+
for param in plugin.get('parameters', []):
897+
param_name = param['name']
898+
if param_name in request.form:
899+
raw_params[param_name] = request.form.get(param_name)
900+
params = validate_parameters(plugin, raw_params)
901+
if 'run' not in plugin:
902+
return jsonify({"error": "Plugin runner not defined"}), 500
903+
result = plugin['run'](params)
904+
return jsonify(result)
1019905
except Exception as e:
1020-
error_message = str(e)
1021-
stack_trace = traceback.format_exc()
1022-
1023-
# Add suggestion for common errors
1024-
suggestion = ""
1025-
if "target" in error_message.lower() and "bits" in error_message.lower():
1026-
suggestion = "Suggestion: Make sure your target state binary string length matches the number of qubits."
1027-
1028-
error_details = f"{error_message}\n\n{stack_trace}\n\n{suggestion}"
1029-
1030-
result = {
1031-
'error': error_details
1032-
}
1033-
1034-
# Return JSON response for AJAX requests
1035-
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
1036-
return jsonify(result)
1037-
1038-
# Render template
1039-
return render_template('plugin.html', plugin=plugin, result=result, educational_content=educational_content, mini_explanation=mini_explanation)
906+
return jsonify({"error": str(e)}), 400
907+
# GET falls back to React
908+
return send_from_directory(app.static_folder, 'index.html')
1040909

1041910
@app.route("/api/plugins", methods=["GET"])
1042911
def api_plugins():
@@ -1083,15 +952,29 @@ def api_run_plugin(plugin_key):
1083952
return jsonify({"error": "Plugin not found"}), 404
1084953
try:
1085954
params = request.get_json()
1086-
result = run_plugin(PLUGINS[plugin_key]["function"], **params)
955+
plugin = PLUGINS[plugin_key]
956+
if 'run' in plugin and callable(plugin['run']):
957+
# Use standardized runner which already wraps results
958+
result = plugin['run'](params or {})
959+
elif 'function' in plugin and callable(plugin['function']):
960+
# Fallback for legacy plugins
961+
result = run_plugin(plugin['function'], _plugin_key=plugin_key, **(params or {}))
962+
else:
963+
return jsonify({"error": "Plugin is misconfigured"}), 500
1087964
return jsonify(result)
1088965
except Exception as e:
1089966
return jsonify({"error": str(e)}), 400
1090967

1091968
@app.route("/api/glossary", methods=["GET"])
1092969
def api_glossary():
1093970
"""Return glossary terms."""
1094-
terms_file = os.path.join(app.static_folder, 'data', 'glossary_terms.json')
971+
# Prefer glossary shipped with the built SPA, then public, then repo static fallback
972+
candidates = [
973+
os.path.join(os.path.dirname(__file__), 'frontend', 'build', 'data', 'glossary_terms.json'),
974+
os.path.join(os.path.dirname(__file__), 'frontend', 'public', 'data', 'glossary_terms.json'),
975+
os.path.join(os.path.dirname(__file__), 'static', 'data', 'glossary_terms.json'),
976+
]
977+
terms_file = next((p for p in candidates if os.path.exists(p)), None)
1095978
try:
1096979
with open(terms_file, 'r') as f:
1097980
terms = json.load(f)
@@ -1157,8 +1040,8 @@ def api_validate(plugin_key):
11571040

11581041
@app.route("/circuit")
11591042
def circuit_designer():
1160-
"""Render the circuit designer template"""
1161-
return render_template("circuit_designer.html")
1043+
# SPA route
1044+
return send_from_directory(app.static_folder, 'index.html')
11621045

11631046
# API routes for Circuit Designer
11641047

@@ -1456,13 +1339,15 @@ def progress_callback(step, total, message):
14561339
# --- Error handling ---
14571340
@app.errorhandler(404)
14581341
def page_not_found(e):
1459-
"""Handle 404 errors."""
1460-
return render_template("error.html", error="Page not found"), 404
1342+
# SPA fallback
1343+
try:
1344+
return send_from_directory(app.static_folder, 'index.html')
1345+
except Exception:
1346+
return jsonify({"error": "Not found"}), 404
14611347

14621348
@app.errorhandler(500)
14631349
def server_error(e):
1464-
"""Handle 500 errors."""
1465-
return render_template("error.html", error="Server error"), 500
1350+
return jsonify({"error": "Server error"}), 500
14661351

14671352
# --- Main entry point ---
14681353
if __name__ == "__main__":

plugins/qrng/qrng.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def generate_random_bit_cirq(qubit_idx=0):
1616
Generates a single random bit using quantum superposition.
1717
1818
In quantum mechanics, a qubit in equal superposition has a 50% chance
19-
of collapsing to |0 or |1 when measured, providing true randomness
19+
of collapsing to |0> or |1> when measured, providing true randomness
2020
based on quantum uncertainty.
2121
2222
Args:
@@ -88,9 +88,9 @@ def generate_random_number_cirq(num_bits=8):
8888
individual_bit_circuits.append(bit_svg)
8989

9090
# Log the quantum state before measurement
91-
# For H|0 state, this is (|0 + |1⟩)/√2
92-
log.append(f"Bit {i}: Prepared initial state |0")
93-
log.append(f"Bit {i}: Applied Hadamard gate to create superposition (|0 + |1⟩)/√2")
91+
# For H|0> state, this is (|0> + |1>)/sqrt(2)
92+
log.append(f"Bit {i}: Prepared initial state |0>")
93+
log.append(f"Bit {i}: Applied Hadamard gate to create superposition (|0> + |1>)/sqrt(2)")
9494
log.append(f"Bit {i}: Measured {bit}")
9595

9696
# Calculate the random number from the bit sequence

0 commit comments

Comments
 (0)