echoboi Claude Sonnet 4.6 commited on
Commit
8adae2c
·
1 Parent(s): cacc292

Fix JSON error: use 1 worker, add error handlers, validate fetch responses

Browse files

- Dockerfile: reduce gunicorn to --workers 1 (stateful in-memory app cannot
share state across multiple processes), increase --threads 4 for concurrency
- genetic_backend.py: add @app .errorhandler(404/500) returning JSON so API
routes always return JSON instead of HTML error pages; wrap all route
handlers in try/except for graceful error reporting
- templates/genetic_evolver.html: validate Content-Type before .json() to
give clear error messages instead of cryptic JSON parse errors; skip
backend call during slider init to avoid race condition on startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (3) hide show
  1. Dockerfile +1 -1
  2. genetic_backend.py +82 -53
  3. templates/genetic_evolver.html +12 -2
Dockerfile CHANGED
@@ -18,4 +18,4 @@ COPY --chown=user . /app
18
  EXPOSE 7860
19
 
20
  # Run Flask app using gunicorn
21
- CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--threads", "2", "--timeout", "120", "genetic_backend:app"]
 
18
  EXPOSE 7860
19
 
20
  # Run Flask app using gunicorn
21
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--threads", "4", "--timeout", "120", "genetic_backend:app"]
genetic_backend.py CHANGED
@@ -790,86 +790,115 @@ class GeneticEquationEvolver:
790
  # Global evolver instance
791
  evolver = GeneticEquationEvolver()
792
 
 
 
 
 
 
 
 
 
793
  @app.route('/')
794
  def index():
795
  return render_template('genetic_evolver.html')
796
 
797
  @app.route('/api/population')
798
  def get_population():
799
- lite = request.args.get('lite', '0') in ('1', 'true', 'True')
800
- return jsonify(evolver.get_population_data(lite=lite))
 
 
 
801
 
802
  @app.route('/api/evolve', methods=['POST'])
803
  def evolve():
804
- data = request.get_json()
805
- feedback_ids = data.get('selected_ids', [])
806
- # Always evolve: empty selection means "Select None" feedback
807
- evolver.evolve_generation(feedback_ids)
808
- return jsonify({'success': True, 'generation': evolver.generation})
 
 
 
809
 
810
  @app.route('/api/set_base_equation', methods=['POST'])
811
  def set_base_equation():
812
- data = request.get_json()
813
- set_name = data.get('set_name')
814
- if evolver.set_base_equation_set(set_name):
815
- return jsonify({'success': True, 'base_set': evolver.current_base_set})
816
- return jsonify({'success': False, 'error': 'Invalid equation set name'}), 400
 
 
 
817
 
818
  @app.route('/api/set_jump_size', methods=['POST'])
819
  def set_jump_size():
820
- data = request.get_json()
821
- jump_size = data.get('jump_size')
822
- if jump_size is not None and isinstance(jump_size, (int, float)) and 0.01 <= jump_size <= 10:
823
- evolver.jump_size_multiplier = float(jump_size)
824
- return jsonify({'success': True, 'jump_size': evolver.jump_size_multiplier})
825
- return jsonify({'success': False, 'error': 'Invalid jump size'}), 400
 
 
 
826
 
827
  @app.route('/api/reset_preferences', methods=['POST'])
828
  def reset_preferences():
829
  """Reset all parameter preferences (color history)"""
830
- evolver.parameter_preferences = {}
831
- return jsonify({'success': True})
 
 
 
832
 
833
  @app.route('/api/reset_all', methods=['POST'])
834
  def reset_all():
835
  """Reset the entire evolution process from the start"""
836
- # Reset all evolution state
837
- evolver.generation = 0
838
- evolver.parameter_preferences = {}
839
- evolver.approved_trig_sites = set()
840
- evolver.liked_variants = []
841
- evolver.select_none_history = []
842
- evolver.mutation_penalties = {}
843
- evolver.feedback_history = []
844
- evolver.elites = []
845
- evolver.next_node_id = 1
846
- evolver.graph = {'nodes': [], 'edges': [], 'feedback': []}
847
- evolver.trig_mutation_prob_base = 1.0 # Reset base to 1.0
848
- evolver.trig_mutation_prob_base_generation = 0 # Reset generation offset
849
- evolver.jump_size_multiplier = 1.0 # Reset jump size to default
850
-
851
- # Reinitialize population from scratch
852
- evolver.initialize_population()
853
-
854
- return jsonify({'success': True, 'generation': evolver.generation})
 
 
 
855
 
856
  @app.route('/api/set_trig_mutation_prob', methods=['POST'])
857
  def set_trig_mutation_prob():
858
  """Set trigonometric mutation probability. Sets base value and generation for auto-decay."""
859
- data = request.get_json()
860
- prob = data.get('trig_mutation_prob')
861
- if prob is None:
862
- # User wants to use current value as new base (resume auto-decay from current)
863
- current_prob = evolver.get_trig_mutation_probability()
864
- evolver.trig_mutation_prob_base = current_prob
865
- evolver.trig_mutation_prob_base_generation = evolver.generation
866
- return jsonify({'success': True, 'trig_mutation_prob': current_prob, 'auto': True})
867
- elif isinstance(prob, (int, float)) and 0.0 <= prob <= 1.0:
868
- # User sets a value - this becomes the new base for auto-decay starting from current generation
869
- evolver.trig_mutation_prob_base = float(prob)
870
- evolver.trig_mutation_prob_base_generation = evolver.generation
871
- return jsonify({'success': True, 'trig_mutation_prob': float(prob), 'auto': False})
872
- return jsonify({'success': False, 'error': 'Invalid probability (must be 0.0-1.0 or null for auto)'}), 400
 
 
 
873
 
874
  if __name__ == '__main__':
875
  app.run(debug=True, host='0.0.0.0', port=5000)
 
790
  # Global evolver instance
791
  evolver = GeneticEquationEvolver()
792
 
793
+ @app.errorhandler(404)
794
+ def not_found(e):
795
+ return jsonify({'error': 'Not found', 'message': str(e)}), 404
796
+
797
+ @app.errorhandler(500)
798
+ def server_error(e):
799
+ return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
800
+
801
  @app.route('/')
802
  def index():
803
  return render_template('genetic_evolver.html')
804
 
805
  @app.route('/api/population')
806
  def get_population():
807
+ try:
808
+ lite = request.args.get('lite', '0') in ('1', 'true', 'True')
809
+ return jsonify(evolver.get_population_data(lite=lite))
810
+ except Exception as e:
811
+ return jsonify({'error': str(e)}), 500
812
 
813
  @app.route('/api/evolve', methods=['POST'])
814
  def evolve():
815
+ try:
816
+ data = request.get_json()
817
+ feedback_ids = data.get('selected_ids', [])
818
+ # Always evolve: empty selection means "Select None" feedback
819
+ evolver.evolve_generation(feedback_ids)
820
+ return jsonify({'success': True, 'generation': evolver.generation})
821
+ except Exception as e:
822
+ return jsonify({'error': str(e)}), 500
823
 
824
  @app.route('/api/set_base_equation', methods=['POST'])
825
  def set_base_equation():
826
+ try:
827
+ data = request.get_json()
828
+ set_name = data.get('set_name')
829
+ if evolver.set_base_equation_set(set_name):
830
+ return jsonify({'success': True, 'base_set': evolver.current_base_set})
831
+ return jsonify({'success': False, 'error': 'Invalid equation set name'}), 400
832
+ except Exception as e:
833
+ return jsonify({'error': str(e)}), 500
834
 
835
  @app.route('/api/set_jump_size', methods=['POST'])
836
  def set_jump_size():
837
+ try:
838
+ data = request.get_json()
839
+ jump_size = data.get('jump_size')
840
+ if jump_size is not None and isinstance(jump_size, (int, float)) and 0.01 <= jump_size <= 10:
841
+ evolver.jump_size_multiplier = float(jump_size)
842
+ return jsonify({'success': True, 'jump_size': evolver.jump_size_multiplier})
843
+ return jsonify({'success': False, 'error': 'Invalid jump size'}), 400
844
+ except Exception as e:
845
+ return jsonify({'error': str(e)}), 500
846
 
847
  @app.route('/api/reset_preferences', methods=['POST'])
848
  def reset_preferences():
849
  """Reset all parameter preferences (color history)"""
850
+ try:
851
+ evolver.parameter_preferences = {}
852
+ return jsonify({'success': True})
853
+ except Exception as e:
854
+ return jsonify({'error': str(e)}), 500
855
 
856
  @app.route('/api/reset_all', methods=['POST'])
857
  def reset_all():
858
  """Reset the entire evolution process from the start"""
859
+ try:
860
+ # Reset all evolution state
861
+ evolver.generation = 0
862
+ evolver.parameter_preferences = {}
863
+ evolver.approved_trig_sites = set()
864
+ evolver.liked_variants = []
865
+ evolver.select_none_history = []
866
+ evolver.mutation_penalties = {}
867
+ evolver.feedback_history = []
868
+ evolver.elites = []
869
+ evolver.next_node_id = 1
870
+ evolver.graph = {'nodes': [], 'edges': [], 'feedback': []}
871
+ evolver.trig_mutation_prob_base = 1.0 # Reset base to 1.0
872
+ evolver.trig_mutation_prob_base_generation = 0 # Reset generation offset
873
+ evolver.jump_size_multiplier = 1.0 # Reset jump size to default
874
+
875
+ # Reinitialize population from scratch
876
+ evolver.initialize_population()
877
+
878
+ return jsonify({'success': True, 'generation': evolver.generation})
879
+ except Exception as e:
880
+ return jsonify({'error': str(e)}), 500
881
 
882
  @app.route('/api/set_trig_mutation_prob', methods=['POST'])
883
  def set_trig_mutation_prob():
884
  """Set trigonometric mutation probability. Sets base value and generation for auto-decay."""
885
+ try:
886
+ data = request.get_json()
887
+ prob = data.get('trig_mutation_prob')
888
+ if prob is None:
889
+ # User wants to use current value as new base (resume auto-decay from current)
890
+ current_prob = evolver.get_trig_mutation_probability()
891
+ evolver.trig_mutation_prob_base = current_prob
892
+ evolver.trig_mutation_prob_base_generation = evolver.generation
893
+ return jsonify({'success': True, 'trig_mutation_prob': current_prob, 'auto': True})
894
+ elif isinstance(prob, (int, float)) and 0.0 <= prob <= 1.0:
895
+ # User sets a value - this becomes the new base for auto-decay starting from current generation
896
+ evolver.trig_mutation_prob_base = float(prob)
897
+ evolver.trig_mutation_prob_base_generation = evolver.generation
898
+ return jsonify({'success': True, 'trig_mutation_prob': float(prob), 'auto': False})
899
+ return jsonify({'success': False, 'error': 'Invalid probability (must be 0.0-1.0 or null for auto)'}), 400
900
+ except Exception as e:
901
+ return jsonify({'error': str(e)}), 500
902
 
903
  if __name__ == '__main__':
904
  app.run(debug=True, host='0.0.0.0', port=5000)
templates/genetic_evolver.html CHANGED
@@ -233,8 +233,15 @@
233
 
234
  function loadPopulation(){
235
  fetch('/api/population')
236
- .then(r=>r.json())
 
 
 
 
 
 
237
  .then(data=>{
 
238
  variants=data.variants;
239
  currentParams = data.base_params || currentParams; // Update base parameters
240
  parameterPreferences = data.parameter_preferences || {}; // Update preferences
@@ -1450,7 +1457,10 @@
1450
  const jumpSlider = document.getElementById('jump-size-slider');
1451
  if(jumpSlider) {
1452
  jumpSlider.value = 3; // Index 3 = 1.0
1453
- updateJumpSize(3);
 
 
 
1454
  }
1455
  loadPopulation();
1456
  </script>
 
233
 
234
  function loadPopulation(){
235
  fetch('/api/population')
236
+ .then(r=>{
237
+ const ct = r.headers.get('content-type')||'';
238
+ if(!ct.includes('application/json')){
239
+ return r.text().then(t=>{throw new Error('Server returned non-JSON (status '+r.status+'). Possible startup issue.');});
240
+ }
241
+ return r.json();
242
+ })
243
  .then(data=>{
244
+ if(data.error){alert('API error: '+data.error);return;}
245
  variants=data.variants;
246
  currentParams = data.base_params || currentParams; // Update base parameters
247
  parameterPreferences = data.parameter_preferences || {}; // Update preferences
 
1457
  const jumpSlider = document.getElementById('jump-size-slider');
1458
  if(jumpSlider) {
1459
  jumpSlider.value = 3; // Index 3 = 1.0
1460
+ // Set local multiplier without sending to backend during init (loadPopulation syncs state)
1461
+ jumpSizeMultiplier = 1.0;
1462
+ const valueDisplay = document.getElementById('jump-size-value');
1463
+ if(valueDisplay) valueDisplay.textContent = '1.00';
1464
  }
1465
  loadPopulation();
1466
  </script>