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>
- Dockerfile +1 -1
- genetic_backend.py +82 -53
- 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", "
|
|
|
|
| 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 |
-
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
| 801 |
|
| 802 |
@app.route('/api/evolve', methods=['POST'])
|
| 803 |
def evolve():
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
|
|
|
|
|
|
|
|
|
| 809 |
|
| 810 |
@app.route('/api/set_base_equation', methods=['POST'])
|
| 811 |
def set_base_equation():
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
|
|
|
|
|
|
|
|
|
| 817 |
|
| 818 |
@app.route('/api/set_jump_size', methods=['POST'])
|
| 819 |
def set_jump_size():
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
|
|
|
|
|
|
|
|
|
| 826 |
|
| 827 |
@app.route('/api/reset_preferences', methods=['POST'])
|
| 828 |
def reset_preferences():
|
| 829 |
"""Reset all parameter preferences (color history)"""
|
| 830 |
-
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
@app.route('/api/reset_all', methods=['POST'])
|
| 834 |
def reset_all():
|
| 835 |
"""Reset the entire evolution process from the start"""
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
|
|
|
|
|
|
|
|
|
| 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=>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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>
|