ivoryos 1.3.8__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/optimizer/ax_optimizer.py +44 -25
- ivoryos/optimizer/base_optimizer.py +15 -1
- ivoryos/optimizer/baybe_optimizer.py +24 -17
- ivoryos/optimizer/nimo_optimizer.py +26 -24
- ivoryos/routes/data/data.py +27 -9
- ivoryos/routes/data/templates/components/step_card.html +47 -11
- ivoryos/routes/data/templates/workflow_view.html +14 -5
- ivoryos/routes/design/design.py +31 -1
- ivoryos/routes/design/design_step.py +2 -1
- ivoryos/routes/design/templates/components/edit_action_form.html +16 -3
- ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
- ivoryos/routes/design/templates/experiment_builder.html +1 -0
- ivoryos/routes/execute/execute.py +36 -7
- ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
- ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
- ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
- ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
- ivoryos/routes/execute/templates/experiment_run.html +0 -264
- ivoryos/socket_handlers.py +1 -1
- ivoryos/static/js/action_handlers.js +252 -118
- ivoryos/static/js/sortable_design.js +1 -0
- ivoryos/utils/bo_campaign.py +17 -16
- ivoryos/utils/db_models.py +122 -18
- ivoryos/utils/decorators.py +1 -0
- ivoryos/utils/form.py +22 -5
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/script_runner.py +447 -147
- ivoryos/utils/utils.py +11 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/METADATA +3 -2
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/RECORD +34 -33
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/WHEEL +0 -0
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -2,397 +2,519 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
|
|
4
4
|
|
|
5
|
+
<h6 class="fw-bold mt-2 mb-1">Load Previous Data</h6>
|
|
6
|
+
|
|
7
|
+
<form method="POST" name="bo" action="{{ url_for('execute.run_bo') }}">
|
|
8
|
+
<div class="container py-2">
|
|
9
|
+
|
|
10
|
+
<!-- Data Loading Section -->
|
|
11
|
+
<div class="input-group mb-3">
|
|
12
|
+
<label class="input-group-text"><i class="bi bi-folder2-open"></i></label>
|
|
13
|
+
<select class="form-select" id="existing_data" name="existing_data">
|
|
14
|
+
<option value="">Load existing data...</option>
|
|
15
|
+
{% for data in data_list %}
|
|
16
|
+
<option value="{{ data }}">{{ data }} </option>
|
|
17
|
+
{% endfor %}
|
|
18
|
+
</select>
|
|
19
|
+
</div>
|
|
5
20
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<form method="POST" name="bo" action="{{ url_for('execute.run_bo') }}">
|
|
17
|
-
<div class="container py-2">
|
|
18
|
-
|
|
19
|
-
<!-- Data Loading Section -->
|
|
20
|
-
<div class="input-group mb-3">
|
|
21
|
-
<label class="input-group-text"><i class="bi bi-folder2-open"></i></label>
|
|
22
|
-
<select class="form-select" id="existing_data" name="existing_data">
|
|
23
|
-
<option value="">Load existing data...</option>
|
|
24
|
-
{% for data in data_list %}
|
|
25
|
-
<option value="{{ data }}">{{ data }} </option>
|
|
26
|
-
{% endfor %}
|
|
27
|
-
</select>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<!-- Data preview section -->
|
|
31
|
-
<div class="row mb-3" id="data_preview_section" style="display: none;">
|
|
32
|
-
<div class="col-12">
|
|
33
|
-
<div class="card">
|
|
34
|
-
<div class="card-header py-2">
|
|
35
|
-
<small class="fw-bold">Data Preview</small>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="card-body py-2">
|
|
38
|
-
<div id="data_preview_content">
|
|
39
|
-
<small class="text-muted">Select a data source to preview</small>
|
|
40
|
-
</div>
|
|
21
|
+
<!-- Data preview section -->
|
|
22
|
+
<div class="row mb-3" id="data_preview_section" style="display: none;">
|
|
23
|
+
<div class="col-12">
|
|
24
|
+
<div class="card">
|
|
25
|
+
<div class="card-header py-2">
|
|
26
|
+
<small class="fw-bold">Data Preview</small>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="card-body py-2">
|
|
29
|
+
<div id="data_preview_content">
|
|
30
|
+
<small class="text-muted">Select a data source to preview</small>
|
|
41
31
|
</div>
|
|
42
32
|
</div>
|
|
43
33
|
</div>
|
|
44
34
|
</div>
|
|
35
|
+
</div>
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
37
|
+
<hr class="my-3">
|
|
38
|
+
<!-- Optimizer Selection -->
|
|
39
|
+
<div class="input-group mb-3">
|
|
40
|
+
<label class="input-group-text"><i class="bi bi-gear"></i></label>
|
|
41
|
+
<select class="form-select" id="optimizer_type" name="optimizer_type" >
|
|
42
|
+
<option value="">Select optimizer...</option>
|
|
43
|
+
{% for optimizer_name, optimizer_info in optimizer_schema.items() %}
|
|
44
|
+
<option value="{{ optimizer_name }}"
|
|
45
|
+
data-multiobjective="{{ optimizer_info.multiple_objectives }}"
|
|
46
|
+
data-parameter-types="{{ optimizer_info.parameter_types|join(',') }}"
|
|
47
|
+
data-optimizer-config="{{ optimizer_info.optimizer_config|tojson|e }}">
|
|
48
|
+
{{ optimizer_name }} {% if optimizer_info.multiple_objectives %}(Multi-objective supported){% endif %}
|
|
49
|
+
</option>
|
|
50
|
+
{% endfor %}
|
|
51
|
+
</select>
|
|
52
|
+
</div>
|
|
53
|
+
<!-- Tabs Navigation -->
|
|
54
|
+
<ul class="nav nav-tabs" id="configTabs" role="tablist">
|
|
55
|
+
<li class="nav-item" role="presentation">
|
|
56
|
+
<button class="nav-link active" id="parameters-tab" data-bs-toggle="tab" data-bs-target="#parameters" type="button" role="tab" aria-controls="parameters" aria-selected="true">
|
|
57
|
+
Parameters
|
|
58
|
+
</button>
|
|
59
|
+
</li>
|
|
60
|
+
<li class="nav-item" role="presentation">
|
|
61
|
+
<button class="nav-link" id="advanced-tab" data-bs-toggle="tab" data-bs-target="#advanced" type="button" role="tab" aria-controls="advanced" aria-selected="false">
|
|
62
|
+
Advanced Settings
|
|
63
|
+
</button>
|
|
64
|
+
</li>
|
|
65
|
+
</ul>
|
|
66
|
+
|
|
67
|
+
<!-- Tab Content -->
|
|
68
|
+
<div class="tab-content" id="configTabContent">
|
|
69
|
+
<!-- Parameters Tab -->
|
|
70
|
+
<div class="tab-pane fade show active" id="parameters" role="tabpanel" aria-labelledby="parameters-tab">
|
|
71
|
+
<div class="py-3">
|
|
72
|
+
|
|
73
|
+
<!-- Parameters -->
|
|
74
|
+
<h6 class="fw-bold mt-2 mb-1">Parameters</h6>
|
|
75
|
+
<div id="parameters_container">
|
|
76
|
+
{% for config in config_list %}
|
|
77
|
+
<div class="row align-items-center mb-2 parameter-row">
|
|
78
|
+
<div class="col-3 col-form-label-sm">
|
|
79
|
+
{{ config }}:
|
|
80
|
+
</div>
|
|
81
|
+
<div class="col-3">
|
|
82
|
+
<select class="form-select form-select-sm parameter-type" id="{{config}}_type" name="{{config}}_type" onchange="updateParameterInputs(this)">
|
|
83
|
+
<!-- Options will be populated by JavaScript -->
|
|
84
|
+
</select>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="col-6 parameter-inputs">
|
|
87
|
+
<input type="text" class="form-control form-control-sm single-input" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">
|
|
88
|
+
<div class="range-inputs" style="display: none;">
|
|
89
|
+
<div class="row g-2">
|
|
90
|
+
<div class="col-4">
|
|
91
|
+
<input type="text" class="form-control form-control-sm" id="{{config}}_min" name="{{config}}_min" placeholder="Min" required>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="col-4">
|
|
94
|
+
<input type="text" class="form-control form-control-sm" id="{{config}}_max" name="{{config}}_max" placeholder="Max" required>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="col-4">
|
|
97
|
+
<input type="text" class="form-control form-control-sm" id="{{config}}_step" name="{{config}}_step" placeholder="Step (optional)">
|
|
103
98
|
</div>
|
|
104
99
|
</div>
|
|
105
100
|
</div>
|
|
106
101
|
</div>
|
|
107
|
-
|
|
102
|
+
</div>
|
|
103
|
+
{% endfor %}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Objectives -->
|
|
107
|
+
<h6 class="fw-bold mt-3 mb-1">Objectives</h6>
|
|
108
|
+
{% for objective in return_list %}
|
|
109
|
+
<div class="row align-items-center mb-2">
|
|
110
|
+
<div class="col-3 col-form-label-sm">
|
|
111
|
+
{{ objective }}:
|
|
112
|
+
</div>
|
|
113
|
+
<div class="col-6">
|
|
114
|
+
<select class="form-select form-select-sm" id="{{objective}}_obj_min" name="{{objective}}_obj_min">
|
|
115
|
+
<option selected>minimize</option>
|
|
116
|
+
<option>maximize</option>
|
|
117
|
+
<option>none</option>
|
|
118
|
+
</select>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="col-3">
|
|
121
|
+
<input class="form-control" type="number" step="any" id="{{objective}}_obj_threshold" name="{{objective}}_obj_threshold" placeholder="Early Stop (optional)">
|
|
122
|
+
</div>
|
|
108
123
|
</div>
|
|
124
|
+
{% endfor %}
|
|
109
125
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<div class="col-6">
|
|
118
|
-
<select class="form-select form-select-sm" id="{{objective}}_min" name="{{objective}}_min">
|
|
119
|
-
<option selected>minimize</option>
|
|
120
|
-
<option>maximize</option>
|
|
121
|
-
<option>none</option>
|
|
122
|
-
</select>
|
|
126
|
+
<!-- Constraints -->
|
|
127
|
+
<div id="constraints_section">
|
|
128
|
+
<h6 class="fw-bold mt-3 mb-1">Constraints</h6>
|
|
129
|
+
<div id="constraints_container">
|
|
130
|
+
<div class="row align-items-center mb-2 constraint-row">
|
|
131
|
+
<div class="col-10">
|
|
132
|
+
<input type="text" class="form-control form-control-sm" name="constraint_expr" placeholder="e.g. a + b <= 80">
|
|
123
133
|
</div>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<input class="form-control" type="number" id="{{objective}}_weight" name="{{objective}}_weight" min="1" max="1000" value="1">
|
|
134
|
+
<div class="col-2">
|
|
135
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addConstraintRow()">+</button>
|
|
127
136
|
</div>
|
|
128
|
-
{% endif %}
|
|
129
137
|
</div>
|
|
130
|
-
{% endfor %}
|
|
131
|
-
|
|
132
|
-
<!-- Budget -->
|
|
133
|
-
<h6 class="fw-bold mt-3 mb-1">Budget</h6>
|
|
134
|
-
<div class="input-group mb-3">
|
|
135
|
-
<label class="input-group-text" for="repeat">Max iteration </label>
|
|
136
|
-
<input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">
|
|
137
138
|
</div>
|
|
138
139
|
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Budget -->
|
|
142
|
+
<h6 class="fw-bold mt-3 mb-1">Budget</h6>
|
|
143
|
+
<div class="input-group mb-3">
|
|
144
|
+
<label class="input-group-text" for="batch_size"># of suggestions </label>
|
|
145
|
+
<input class="form-control" type="number" id="batch_size" name="batch_size" min="1" max="100" value="1">
|
|
146
|
+
<label class="input-group-text" for="repeat">Max iteration </label>
|
|
147
|
+
<input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">
|
|
148
|
+
</div>
|
|
139
149
|
</div>
|
|
150
|
+
</div>
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
<!-- Advanced Settings Tab -->
|
|
153
|
+
<div class="tab-pane fade" id="advanced" role="tabpanel" aria-labelledby="advanced-tab">
|
|
154
|
+
<div class="py-3">
|
|
155
|
+
<h6 class="fw-bold mt-2 mb-1">Optimizer Configuration</h6>
|
|
156
|
+
<div id="optimizer_config_container" style="display: none;">
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
<!-- Step 1 Configuration -->
|
|
159
|
+
<div class="card mb-3">
|
|
160
|
+
<div class="card-header py-2">
|
|
161
|
+
<small class="fw-bold">Step 1 Configuration</small>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="card-body py-2">
|
|
164
|
+
<div class="row align-items-center mb-2">
|
|
165
|
+
<div class="col-3 col-form-label-sm">
|
|
166
|
+
Model:
|
|
167
|
+
</div>
|
|
168
|
+
<div class="col-6">
|
|
169
|
+
<select class="form-select form-select-sm" id="step1_model" name="step1_model">
|
|
170
|
+
<!-- Options will be populated by JavaScript -->
|
|
171
|
+
</select>
|
|
172
|
+
</div>
|
|
151
173
|
</div>
|
|
152
|
-
<div class="
|
|
153
|
-
<div class="
|
|
154
|
-
|
|
155
|
-
Model:
|
|
156
|
-
</div>
|
|
157
|
-
<div class="col-6">
|
|
158
|
-
<select class="form-select form-select-sm" id="step1_model" name="step1_model">
|
|
159
|
-
<!-- Options will be populated by JavaScript -->
|
|
160
|
-
</select>
|
|
161
|
-
</div>
|
|
174
|
+
<div class="row align-items-center mb-2" id="step1_num_samples_row">
|
|
175
|
+
<div class="col-3 col-form-label-sm">
|
|
176
|
+
Num Samples:
|
|
162
177
|
</div>
|
|
163
|
-
<div class="
|
|
164
|
-
<
|
|
165
|
-
Num Samples:
|
|
166
|
-
</div>
|
|
167
|
-
<div class="col-6">
|
|
168
|
-
<input type="number" class="form-control form-control-sm" id="step1_num_samples" name="step1_num_samples" min="1" value="">
|
|
169
|
-
</div>
|
|
178
|
+
<div class="col-6">
|
|
179
|
+
<input type="number" class="form-control form-control-sm" id="step1_num_samples" name="step1_num_samples" min="0" value="">
|
|
170
180
|
</div>
|
|
171
181
|
</div>
|
|
172
182
|
</div>
|
|
183
|
+
</div>
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
</div>
|
|
185
|
+
<!-- Step 2 Configuration -->
|
|
186
|
+
<div class="card mb-3">
|
|
187
|
+
<div class="card-header py-2">
|
|
188
|
+
<small class="fw-bold">Step 2 Configuration</small>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="card-body py-2">
|
|
191
|
+
<div class="row align-items-center mb-2">
|
|
192
|
+
<div class="col-3 col-form-label-sm">
|
|
193
|
+
Model:
|
|
194
|
+
</div>
|
|
195
|
+
<div class="col-6">
|
|
196
|
+
<select class="form-select form-select-sm" id="step2_model" name="step2_model">
|
|
197
|
+
<!-- Options will be populated by JavaScript -->
|
|
198
|
+
</select>
|
|
189
199
|
</div>
|
|
190
200
|
</div>
|
|
191
201
|
</div>
|
|
192
202
|
</div>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
</
|
|
203
|
+
</div>
|
|
204
|
+
<div id="optimizer_config_placeholder">
|
|
205
|
+
<small class="text-muted">Select an optimizer to configure advanced settings</small>
|
|
196
206
|
</div>
|
|
197
207
|
</div>
|
|
198
208
|
</div>
|
|
199
|
-
|
|
200
|
-
{% if not no_deck_warning%}
|
|
201
|
-
<div class="input-group mb-3 mt-3">
|
|
202
|
-
<button class="form-control" type="submit" name="bo">Run</button>
|
|
203
|
-
</div>
|
|
204
|
-
{% endif %}
|
|
205
209
|
</div>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
previewContent.innerHTML = '<small class="text-danger">Failed to load preview.</small>';
|
|
210
|
+
|
|
211
|
+
{% if not no_deck_warning%}
|
|
212
|
+
<div class="input-group mb-3 mt-3">
|
|
213
|
+
<button class="form-control" type="submit" name="bo">Run</button>
|
|
214
|
+
</div>
|
|
215
|
+
{% endif %}
|
|
216
|
+
</div>
|
|
217
|
+
</form>
|
|
218
|
+
|
|
219
|
+
<script>
|
|
220
|
+
const optimizerSchema = {{ optimizer_schema|tojson }};
|
|
221
|
+
|
|
222
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
223
|
+
const dataSelect = document.getElementById('existing_data');
|
|
224
|
+
const previewSection = document.getElementById('data_preview_section');
|
|
225
|
+
const previewContent = document.getElementById('data_preview_content');
|
|
226
|
+
|
|
227
|
+
// Data preview functionality
|
|
228
|
+
dataSelect.addEventListener('change', function() {
|
|
229
|
+
const filename = dataSelect.value;
|
|
230
|
+
if (!filename) {
|
|
231
|
+
previewSection.style.display = 'none';
|
|
232
|
+
previewContent.innerHTML = '<small class="text-muted">Select a data source to preview</small>';
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
fetch('{{ url_for("execute.data_preview", filename="FILENAME") }}'.replace('FILENAME', encodeURIComponent(filename)))
|
|
236
|
+
.then(response => {
|
|
237
|
+
if (!response.ok) throw new Error('Network response was not ok');
|
|
238
|
+
return response.json();
|
|
239
|
+
})
|
|
240
|
+
.then(data => {
|
|
241
|
+
previewSection.style.display = '';
|
|
242
|
+
if (!data.rows || data.rows.length === 0) {
|
|
243
|
+
previewContent.innerHTML = '<small class="text-muted">No data found in file.</small>';
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
let html = '<table class="table table-sm table-bordered mb-0"><thead><tr>';
|
|
247
|
+
data.columns.forEach(col => html += `<th>${col}</th>`);
|
|
248
|
+
html += '</tr></thead><tbody>';
|
|
249
|
+
data.rows.forEach(row => {
|
|
250
|
+
html += '<tr>';
|
|
251
|
+
data.columns.forEach(col => html += `<td>${row[col] || ''}</td>`);
|
|
252
|
+
html += '</tr>';
|
|
250
253
|
});
|
|
251
|
-
|
|
254
|
+
html += '</tbody></table>';
|
|
255
|
+
previewContent.innerHTML = html;
|
|
256
|
+
})
|
|
257
|
+
.catch(() => {
|
|
258
|
+
previewSection.style.display = '';
|
|
259
|
+
previewContent.innerHTML = '<small class="text-danger">Failed to load preview.</small>';
|
|
260
|
+
});
|
|
252
261
|
});
|
|
262
|
+
});
|
|
253
263
|
|
|
254
|
-
function updateOptimizerInfo() {
|
|
255
|
-
const optimizerSelect = document.getElementById('optimizer_type');
|
|
256
|
-
const selectedOption = optimizerSelect.selectedOptions[0];
|
|
257
264
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
document.getElementById('optimizer_config_placeholder').style.display = 'block';
|
|
262
|
-
resetParameterTypes();
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
+
function updateOptimizerConfig(config) {
|
|
266
|
+
const container = document.getElementById('optimizer_config_container');
|
|
267
|
+
const placeholder = document.getElementById('optimizer_config_placeholder');
|
|
265
268
|
|
|
266
|
-
|
|
267
|
-
const parameterTypes = selectedOption.dataset.parameterTypes.split(',');
|
|
268
|
-
updateParameterTypeOptions(parameterTypes);
|
|
269
|
+
console.log('Updating optimizer config:', config);
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
document.getElementById('optimizer_config_container').style.display = 'none';
|
|
276
|
-
document.getElementById('optimizer_config_placeholder').style.display = 'block';
|
|
277
|
-
}
|
|
271
|
+
if (!config || !config.step_1) {
|
|
272
|
+
console.error('Invalid optimizer config:', config);
|
|
273
|
+
container.style.display = 'none';
|
|
274
|
+
placeholder.style.display = 'block';
|
|
275
|
+
return;
|
|
278
276
|
}
|
|
279
277
|
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
placeholder.style.display = 'none';
|
|
279
|
+
container.style.display = 'block';
|
|
280
|
+
|
|
281
|
+
// Update Step 1
|
|
282
|
+
const step1ModelSelect = document.getElementById('step1_model');
|
|
283
|
+
if (step1ModelSelect && config.step_1.model) {
|
|
284
|
+
step1ModelSelect.innerHTML = '';
|
|
285
|
+
config.step_1.model.forEach(model => {
|
|
286
|
+
const option = document.createElement('option');
|
|
287
|
+
option.value = model;
|
|
288
|
+
option.textContent = model;
|
|
289
|
+
step1ModelSelect.appendChild(option);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
282
292
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
293
|
+
// Update Step 1 num_samples if exists
|
|
294
|
+
const step1NumSamplesRow = document.getElementById('step1_num_samples_row');
|
|
295
|
+
const step1NumSamplesInput = document.getElementById('step1_num_samples');
|
|
296
|
+
if (step1NumSamplesRow && step1NumSamplesInput) {
|
|
297
|
+
if (config.step_1.num_samples !== undefined) {
|
|
298
|
+
step1NumSamplesRow.style.display = '';
|
|
299
|
+
step1NumSamplesInput.value = config.step_1.num_samples;
|
|
300
|
+
} else {
|
|
301
|
+
step1NumSamplesRow.style.display = 'none';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
286
304
|
|
|
287
|
-
|
|
288
|
-
|
|
305
|
+
// Update Step 2
|
|
306
|
+
if (config.step_2) {
|
|
307
|
+
const step2ModelSelect = document.getElementById('step2_model');
|
|
308
|
+
if (step2ModelSelect && config.step_2.model) {
|
|
309
|
+
step2ModelSelect.innerHTML = '';
|
|
310
|
+
config.step_2.model.forEach(model => {
|
|
289
311
|
const option = document.createElement('option');
|
|
290
|
-
option.value =
|
|
291
|
-
option.textContent =
|
|
292
|
-
|
|
293
|
-
option.selected = true;
|
|
294
|
-
}
|
|
295
|
-
select.appendChild(option);
|
|
312
|
+
option.value = model;
|
|
313
|
+
option.textContent = model;
|
|
314
|
+
step2ModelSelect.appendChild(option);
|
|
296
315
|
});
|
|
297
|
-
|
|
298
|
-
// Update inputs for the current selection
|
|
299
|
-
updateParameterInputs(select);
|
|
300
|
-
});
|
|
316
|
+
}
|
|
301
317
|
}
|
|
318
|
+
}
|
|
302
319
|
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
321
|
+
console.log("Tab3: Init");
|
|
305
322
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const option = document.createElement('option');
|
|
311
|
-
option.value = type;
|
|
312
|
-
option.textContent = type;
|
|
313
|
-
if (type === 'range') {
|
|
314
|
-
option.selected = true;
|
|
315
|
-
}
|
|
316
|
-
select.appendChild(option);
|
|
317
|
-
});
|
|
323
|
+
const FORM_STATE_KEY = "bo_form_state";
|
|
324
|
+
const form = document.querySelector('form[name="bo"]');
|
|
325
|
+
const optimizerSelect = document.getElementById("optimizer_type");
|
|
326
|
+
let saveTimeout = null;
|
|
318
327
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
328
|
+
const saveFormData = () => {
|
|
329
|
+
if (!form) return;
|
|
322
330
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
331
|
+
clearTimeout(saveTimeout);
|
|
332
|
+
saveTimeout = setTimeout(() => {
|
|
333
|
+
const formData = new FormData(form);
|
|
334
|
+
const data = {};
|
|
327
335
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
336
|
+
// Convert FormData to object
|
|
337
|
+
for (let [key, value] of formData.entries()) {
|
|
338
|
+
data[key] = value;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
sessionStorage.setItem(FORM_STATE_KEY, JSON.stringify(data));
|
|
343
|
+
console.log('Tab3: Saved form data', Object.keys(data).length, 'fields');
|
|
344
|
+
} catch (e) {
|
|
345
|
+
console.warn('Tab3: Could not save form data:', e);
|
|
346
|
+
}
|
|
347
|
+
}, 500); // Debounce save
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const loadSavedData = () => {
|
|
351
|
+
try {
|
|
352
|
+
const saved = sessionStorage.getItem(FORM_STATE_KEY);
|
|
353
|
+
if (saved) {
|
|
354
|
+
const data = JSON.parse(saved);
|
|
355
|
+
console.log('Tab3: Found saved data', Object.keys(data).length, 'fields');
|
|
356
|
+
return data;
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
console.warn('Tab3: Could not load saved data:', e);
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const restoreState = () => {
|
|
365
|
+
const data = loadSavedData();
|
|
366
|
+
if (!data) {
|
|
367
|
+
console.log('Tab3: No saved data to restore');
|
|
368
|
+
return;
|
|
334
369
|
}
|
|
335
|
-
}
|
|
336
370
|
|
|
337
|
-
|
|
338
|
-
const container = document.getElementById('optimizer_config_container');
|
|
339
|
-
const placeholder = document.getElementById('optimizer_config_placeholder');
|
|
371
|
+
console.log('Tab3: Restoring form state');
|
|
340
372
|
|
|
341
|
-
|
|
373
|
+
// First restore the optimizer selection
|
|
374
|
+
if (data.optimizer_type && optimizerSelect) {
|
|
375
|
+
optimizerSelect.value = data.optimizer_type;
|
|
376
|
+
console.log('Tab3: Restored optimizer:', data.optimizer_type);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Trigger optimizer update to populate step dropdowns
|
|
380
|
+
if (optimizerSelect && optimizerSelect.value) {
|
|
381
|
+
updateOptimizerInfo();
|
|
382
|
+
}
|
|
342
383
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
384
|
+
// Small delay to ensure dropdowns are populated
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
// Then restore all other form values (including step selections)
|
|
387
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
388
|
+
const el = form.querySelector(`[name="${key}"]`);
|
|
389
|
+
if (el) {
|
|
390
|
+
el.value = value;
|
|
391
|
+
console.log('Tab3: Restored', key, '=', value);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}, 100);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const updateOptimizerInfo = () => {
|
|
398
|
+
const opt = optimizerSelect.value;
|
|
399
|
+
if (!opt) {
|
|
400
|
+
// Hide optimizer config when no optimizer selected
|
|
401
|
+
document.getElementById('optimizer_config_container').style.display = 'none';
|
|
402
|
+
document.getElementById('optimizer_config_placeholder').style.display = 'block';
|
|
346
403
|
return;
|
|
347
404
|
}
|
|
348
405
|
|
|
349
|
-
|
|
350
|
-
|
|
406
|
+
const schema = optimizerSchema[opt];
|
|
407
|
+
if (!schema) return;
|
|
351
408
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const option = document.createElement('option');
|
|
358
|
-
option.value = model;
|
|
359
|
-
option.textContent = model;
|
|
360
|
-
step1ModelSelect.appendChild(option);
|
|
361
|
-
});
|
|
409
|
+
console.log('Tab3: Updating optimizer info for', opt);
|
|
410
|
+
|
|
411
|
+
// Update optimizer config (step 1 and step 2)
|
|
412
|
+
if (schema.optimizer_config) {
|
|
413
|
+
updateOptimizerConfig(schema.optimizer_config);
|
|
362
414
|
}
|
|
363
415
|
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
416
|
+
const supportsContinuous = schema.supports_continuous !== false;
|
|
417
|
+
const available = schema.parameter_types || ["range", "choice", "fixed"];
|
|
418
|
+
|
|
419
|
+
document.querySelectorAll(".parameter-type").forEach(sel => {
|
|
420
|
+
const currentValue = sel.value; // Preserve current value if valid
|
|
421
|
+
sel.innerHTML = available.map(t => `<option value="${t}">${t}</option>`).join("");
|
|
422
|
+
|
|
423
|
+
// Restore previous value if it's still valid, otherwise use default
|
|
424
|
+
if (available.includes(currentValue)) {
|
|
425
|
+
sel.value = currentValue;
|
|
371
426
|
} else {
|
|
372
|
-
|
|
427
|
+
sel.value = available.includes("range") ? "range" : available[0];
|
|
373
428
|
}
|
|
374
|
-
}
|
|
375
429
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
430
|
+
updateParameterInputs(sel, supportsContinuous);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
document.getElementById("constraints_section").style.display =
|
|
434
|
+
schema.supports_constraints === false ? "none" : "block";
|
|
435
|
+
|
|
436
|
+
saveFormData();
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const updateParameterInputs = (selectElement, supportsContinuous = true) => {
|
|
440
|
+
const row = selectElement.closest(".parameter-row");
|
|
441
|
+
const single = row.querySelector(".single-input");
|
|
442
|
+
const range = row.querySelector(".range-inputs");
|
|
443
|
+
const isRange = selectElement.value === "range";
|
|
444
|
+
|
|
445
|
+
// Toggle visibility
|
|
446
|
+
single.style.display = isRange ? "none" : "block";
|
|
447
|
+
range.style.display = isRange ? "block" : "none";
|
|
448
|
+
|
|
449
|
+
// Handle "step" input rule
|
|
450
|
+
const stepInput = range?.querySelector('input[name$="_step"]');
|
|
451
|
+
if (stepInput) {
|
|
452
|
+
if (!supportsContinuous) {
|
|
453
|
+
stepInput.required = true;
|
|
454
|
+
stepInput.placeholder = "Step (required)";
|
|
455
|
+
} else {
|
|
456
|
+
stepInput.required = false;
|
|
457
|
+
stepInput.placeholder = "Step (optional)";
|
|
387
458
|
}
|
|
388
459
|
}
|
|
389
|
-
}
|
|
460
|
+
};
|
|
390
461
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
462
|
+
// Bind events
|
|
463
|
+
if (optimizerSelect) {
|
|
464
|
+
optimizerSelect.addEventListener("change", () => {
|
|
465
|
+
updateOptimizerInfo();
|
|
466
|
+
saveFormData();
|
|
396
467
|
});
|
|
397
|
-
}
|
|
398
|
-
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (form) {
|
|
471
|
+
// Save on any input change
|
|
472
|
+
form.addEventListener("input", saveFormData);
|
|
473
|
+
form.addEventListener("change", saveFormData);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
document.querySelectorAll(".parameter-type").forEach(sel =>
|
|
477
|
+
sel.addEventListener("change", e => {
|
|
478
|
+
const opt = optimizerSelect.value;
|
|
479
|
+
const schema = opt ? optimizerSchema[opt] : null;
|
|
480
|
+
const supportsContinuous = schema ? (schema.supports_continuous !== false) : true;
|
|
481
|
+
updateParameterInputs(e.target, supportsContinuous);
|
|
482
|
+
saveFormData();
|
|
483
|
+
})
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// Handle page unload
|
|
487
|
+
window.addEventListener('beforeunload', saveFormData);
|
|
488
|
+
|
|
489
|
+
console.log("Tab3: Init Done");
|
|
490
|
+
|
|
491
|
+
// Restore state after all event listeners are set up
|
|
492
|
+
restoreState();
|
|
493
|
+
|
|
494
|
+
// ============================================
|
|
495
|
+
// EXPOSE FUNCTIONS GLOBALLY FOR COORDINATION
|
|
496
|
+
// ============================================
|
|
497
|
+
window.saveBO = saveFormData;
|
|
498
|
+
window.loadBO = restoreState;
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
function addConstraintRow() {
|
|
502
|
+
const container = document.getElementById('constraints_container');
|
|
503
|
+
const newRow = document.createElement('div');
|
|
504
|
+
newRow.classList.add('row', 'align-items-center', 'mb-2', 'constraint-row');
|
|
505
|
+
newRow.innerHTML = `
|
|
506
|
+
<div class="col-10">
|
|
507
|
+
<input type="text" class="form-control form-control-sm" name="constraint_expr" placeholder="e.g. a + b <= 80">
|
|
508
|
+
</div>
|
|
509
|
+
<div class="col-2">
|
|
510
|
+
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeConstraintRow(this)">−</button>
|
|
511
|
+
</div>
|
|
512
|
+
`;
|
|
513
|
+
container.appendChild(newRow);
|
|
514
|
+
}
|
|
515
|
+
function removeConstraintRow(button) {
|
|
516
|
+
button.closest('.constraint-row').remove();
|
|
517
|
+
}
|
|
518
|
+
</script>
|
|
519
|
+
|
|
520
|
+
</div>
|