@@ -83,11 +83,11 @@ def configure_logging():
8383# Create application logger
8484logger = 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 = '/' )
8888CORS (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 ("/" )
767774def 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' )
780779def sitemap ():
@@ -839,21 +838,8 @@ def sitemap():
839838
840839@app .route ("/sitemap" )
841840def 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' )
859845def robots ():
@@ -876,51 +862,11 @@ def robots():
876862
877863@app .route ("/category/<category>" )
878864def 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" )
899868def 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
926872def add_cache_headers (response ):
@@ -940,103 +886,26 @@ def add_cache_headers(response):
940886
941887@app .route ("/plugin/<plugin_key>" , methods = ["GET" , "POST" ])
942888def 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" ])
1042911def 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" ])
1092969def 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" )
11591042def 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 )
14581341def 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 )
14631349def 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 ---
14681353if __name__ == "__main__" :
0 commit comments