ivoryos 1.3.9__py3-none-any.whl → 1.4.1__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 +47 -25
- ivoryos/optimizer/base_optimizer.py +19 -1
- ivoryos/optimizer/baybe_optimizer.py +27 -17
- ivoryos/optimizer/nimo_optimizer.py +25 -16
- 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 +71 -13
- ivoryos/routes/execute/templates/components/logging_panel.html +50 -25
- 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 +32 -12
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/script_runner.py +436 -143
- ivoryos/utils/utils.py +11 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.3.9.dist-info → ivoryos-1.4.1.dist-info}/METADATA +6 -4
- {ivoryos-1.3.9.dist-info → ivoryos-1.4.1.dist-info}/RECORD +35 -34
- {ivoryos-1.3.9.dist-info → ivoryos-1.4.1.dist-info}/WHEEL +0 -0
- {ivoryos-1.3.9.dist-info → ivoryos-1.4.1.dist-info}/licenses/LICENSE +0 -0
- {ivoryos-1.3.9.dist-info → ivoryos-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -1,118 +1,169 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// STATE MANAGEMENT
|
|
3
|
+
// ============================================================================
|
|
1
4
|
|
|
5
|
+
let previousHtmlState = null; // Store previous instrument panel state
|
|
6
|
+
let lastFocusedElement = null; // Track focus for modal management
|
|
2
7
|
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// MODE & BATCH MANAGEMENT
|
|
10
|
+
// ============================================================================
|
|
3
11
|
|
|
12
|
+
function getMode() {
|
|
13
|
+
return sessionStorage.getItem("mode") || "single";
|
|
14
|
+
}
|
|
4
15
|
|
|
5
|
-
function
|
|
6
|
-
|
|
16
|
+
function setMode(mode, triggerUpdate = true) {
|
|
17
|
+
sessionStorage.setItem("mode", mode);
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
.catch(err => {
|
|
25
|
-
console.error("Save error:", err);
|
|
26
|
-
alert("Something went wrong.");
|
|
27
|
-
});
|
|
19
|
+
const modeButtons = document.querySelectorAll(".mode-toggle");
|
|
20
|
+
const batchOptions = document.getElementById("batch-options");
|
|
21
|
+
|
|
22
|
+
modeButtons.forEach(b => b.classList.toggle("active", b.dataset.mode === mode));
|
|
23
|
+
|
|
24
|
+
if (batchOptions) {
|
|
25
|
+
batchOptions.style.display = (mode === "batch") ? "inline-flex" : "none";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (triggerUpdate) updateCode();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getBatch() {
|
|
32
|
+
return sessionStorage.getItem("batch") || "sample";
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
function setBatch(batch, triggerUpdate = true) {
|
|
36
|
+
sessionStorage.setItem("batch", batch);
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.then(data => {
|
|
36
|
-
if (data.html) {
|
|
37
|
-
document.getElementById("sidebar-wrapper").innerHTML = data.html;
|
|
38
|
-
initializeDragHandlers()
|
|
39
|
-
}
|
|
40
|
-
})
|
|
38
|
+
const batchButtons = document.querySelectorAll(".batch-toggle");
|
|
39
|
+
batchButtons.forEach(b => b.classList.toggle("active", b.dataset.batch === batch));
|
|
40
|
+
|
|
41
|
+
if (triggerUpdate) updateCode();
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// CODE OVERLAY MANAGEMENT
|
|
46
|
+
// ============================================================================
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
async function updateCode() {
|
|
49
|
+
try {
|
|
50
|
+
const params = new URLSearchParams({ mode: getMode(), batch: getBatch() });
|
|
51
|
+
const res = await fetch(scriptCompileUrl + "?" + params.toString());
|
|
52
|
+
if (!res.ok) return;
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
hideModal();
|
|
57
|
-
} else {
|
|
58
|
-
alert("Failed to add method: " + data.error);
|
|
54
|
+
const data = await res.json();
|
|
55
|
+
const codeElem = document.getElementById("python-code");
|
|
56
|
+
|
|
57
|
+
codeElem.removeAttribute("data-highlighted"); // Reset highlight.js flag
|
|
58
|
+
codeElem.textContent = data.code['script'] || "# No code found";
|
|
59
|
+
|
|
60
|
+
if (window.hljs) {
|
|
61
|
+
hljs.highlightElement(codeElem);
|
|
59
62
|
}
|
|
60
|
-
})
|
|
61
|
-
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("Error updating code:", err);
|
|
65
|
+
}
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
function initializeCodeOverlay() {
|
|
69
|
+
const codeElem = document.getElementById("python-code");
|
|
70
|
+
const copyBtn = document.getElementById("copy-code");
|
|
71
|
+
const downloadBtn = document.getElementById("download-code");
|
|
72
|
+
|
|
73
|
+
if (!copyBtn || !downloadBtn) return; // Elements don't exist
|
|
74
|
+
|
|
75
|
+
// Remove old listeners by cloning (prevents duplicate bindings)
|
|
76
|
+
const newCopyBtn = copyBtn.cloneNode(true);
|
|
77
|
+
const newDownloadBtn = downloadBtn.cloneNode(true);
|
|
78
|
+
copyBtn.parentNode.replaceChild(newCopyBtn, copyBtn);
|
|
79
|
+
downloadBtn.parentNode.replaceChild(newDownloadBtn, downloadBtn);
|
|
80
|
+
|
|
81
|
+
// Copy to clipboard
|
|
82
|
+
newCopyBtn.addEventListener("click", () => {
|
|
83
|
+
navigator.clipboard.writeText(codeElem.textContent)
|
|
84
|
+
.then(() => alert("Code copied!"))
|
|
85
|
+
.catch(err => console.error("Failed to copy", err));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Download current code
|
|
89
|
+
newDownloadBtn.addEventListener("click", () => {
|
|
90
|
+
const blob = new Blob([codeElem.textContent], { type: "text/plain" });
|
|
91
|
+
const url = URL.createObjectURL(blob);
|
|
92
|
+
const a = document.createElement("a");
|
|
93
|
+
a.href = url;
|
|
94
|
+
a.download = "script.py";
|
|
95
|
+
a.click();
|
|
96
|
+
URL.revokeObjectURL(url);
|
|
97
|
+
});
|
|
98
|
+
updateCode();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// UI UPDATE FUNCTIONS
|
|
103
|
+
// ============================================================================
|
|
64
104
|
|
|
65
105
|
function updateActionCanvas(html) {
|
|
66
106
|
document.getElementById("canvas-action-wrapper").innerHTML = html;
|
|
67
|
-
initializeCanvas();
|
|
68
|
-
|
|
69
|
-
|
|
107
|
+
initializeCanvas();
|
|
108
|
+
|
|
109
|
+
const mode = getMode();
|
|
110
|
+
const batch = getBatch();
|
|
111
|
+
|
|
112
|
+
// Rebind event handlers for mode/batch toggles
|
|
113
|
+
document.querySelectorAll(".mode-toggle").forEach(btn => {
|
|
114
|
+
btn.addEventListener("click", () => setMode(btn.dataset.mode));
|
|
115
|
+
});
|
|
116
|
+
document.querySelectorAll(".batch-toggle").forEach(btn => {
|
|
117
|
+
btn.addEventListener("click", () => setBatch(btn.dataset.batch));
|
|
70
118
|
});
|
|
71
|
-
}
|
|
72
119
|
|
|
120
|
+
// Restore toggle UI state (without triggering updates)
|
|
121
|
+
setMode(mode, false);
|
|
122
|
+
setBatch(batch, false);
|
|
73
123
|
|
|
74
|
-
|
|
124
|
+
// Reinitialize code overlay buttons
|
|
125
|
+
initializeCodeOverlay();
|
|
75
126
|
|
|
127
|
+
}
|
|
76
128
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
function updateInstrumentPanel(link) {
|
|
130
|
+
const url = link.dataset.getUrl;
|
|
131
|
+
|
|
132
|
+
fetch(url)
|
|
133
|
+
.then(res => res.json())
|
|
134
|
+
.then(data => {
|
|
135
|
+
if (data.html) {
|
|
136
|
+
document.getElementById("sidebar-wrapper").innerHTML = data.html;
|
|
137
|
+
initializeDragHandlers();
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.catch(err => console.error("Error updating instrument panel:", err));
|
|
85
141
|
}
|
|
86
142
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const formData = new FormData(form);
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// WORKFLOW MANAGEMENT
|
|
145
|
+
// ============================================================================
|
|
91
146
|
|
|
92
|
-
|
|
147
|
+
function saveWorkflow(link) {
|
|
148
|
+
const url = link.dataset.postUrl;
|
|
149
|
+
|
|
150
|
+
fetch(url, {
|
|
93
151
|
method: 'POST',
|
|
94
|
-
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json'
|
|
154
|
+
}
|
|
95
155
|
})
|
|
96
|
-
.then(
|
|
97
|
-
.then(
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (previousHtmlState) {
|
|
103
|
-
document.getElementById('instrument-panel').innerHTML = previousHtmlState;
|
|
104
|
-
previousHtmlState = null; // Clear the stored state
|
|
105
|
-
}
|
|
106
|
-
const parser = new DOMParser();
|
|
107
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
108
|
-
const warningDiv = doc.querySelector('#warning');
|
|
109
|
-
if (warningDiv && warningDiv.textContent.trim()) {
|
|
110
|
-
alert(warningDiv.textContent.trim()); // or use a nicer toast
|
|
111
|
-
}
|
|
156
|
+
.then(res => res.json())
|
|
157
|
+
.then(data => {
|
|
158
|
+
if (data.success) {
|
|
159
|
+
window.location.reload();
|
|
160
|
+
} else {
|
|
161
|
+
alert("Failed to save workflow: " + data.error);
|
|
112
162
|
}
|
|
113
163
|
})
|
|
114
|
-
.catch(
|
|
115
|
-
console.error(
|
|
164
|
+
.catch(err => {
|
|
165
|
+
console.error("Save error:", err);
|
|
166
|
+
alert("Something went wrong.");
|
|
116
167
|
});
|
|
117
168
|
}
|
|
118
169
|
|
|
@@ -134,33 +185,26 @@ function clearDraft() {
|
|
|
134
185
|
.catch(error => console.error("Failed to clear draft", error));
|
|
135
186
|
}
|
|
136
187
|
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// ACTION MANAGEMENT (CRUD Operations)
|
|
190
|
+
// ============================================================================
|
|
137
191
|
|
|
192
|
+
function addMethodToDesign(event, form) {
|
|
193
|
+
event.preventDefault();
|
|
138
194
|
|
|
195
|
+
const formData = new FormData(form);
|
|
139
196
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
function duplicateAction(uuid) {
|
|
143
|
-
if (!uuid) {
|
|
144
|
-
console.error('Invalid UUID');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
fetch(scriptStepDupUrl.replace('0', uuid), {
|
|
197
|
+
fetch(form.action, {
|
|
149
198
|
method: 'POST',
|
|
150
|
-
|
|
151
|
-
'Content-Type': 'application/json'
|
|
152
|
-
}
|
|
199
|
+
body: formData
|
|
153
200
|
})
|
|
154
|
-
|
|
155
|
-
.then(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const warningDiv = doc.querySelector('#warning');
|
|
162
|
-
if (warningDiv && warningDiv.textContent.trim()) {
|
|
163
|
-
alert(warningDiv.textContent.trim()); // or use a nicer toast
|
|
201
|
+
.then(response => response.json())
|
|
202
|
+
.then(data => {
|
|
203
|
+
if (data.success) {
|
|
204
|
+
updateActionCanvas(data.html);
|
|
205
|
+
hideModal();
|
|
206
|
+
} else {
|
|
207
|
+
alert("Failed to add method: " + data.error);
|
|
164
208
|
}
|
|
165
209
|
})
|
|
166
210
|
.catch(error => console.error('Error:', error));
|
|
@@ -172,10 +216,11 @@ function editAction(uuid) {
|
|
|
172
216
|
return;
|
|
173
217
|
}
|
|
174
218
|
|
|
219
|
+
// Store current state for rollback
|
|
175
220
|
previousHtmlState = document.getElementById('instrument-panel').innerHTML;
|
|
176
221
|
|
|
177
222
|
fetch(scriptStepUrl.replace('0', uuid), {
|
|
178
|
-
method: 'GET',
|
|
223
|
+
method: 'GET',
|
|
179
224
|
headers: {
|
|
180
225
|
'Content-Type': 'application/json'
|
|
181
226
|
}
|
|
@@ -184,9 +229,9 @@ function editAction(uuid) {
|
|
|
184
229
|
if (!response.ok) {
|
|
185
230
|
return response.json().then(err => {
|
|
186
231
|
if (err.warning) {
|
|
187
|
-
alert(err.warning);
|
|
232
|
+
alert(err.warning);
|
|
188
233
|
}
|
|
189
|
-
//
|
|
234
|
+
// Restore panel so user isn't stuck
|
|
190
235
|
if (previousHtmlState) {
|
|
191
236
|
document.getElementById('instrument-panel').innerHTML = previousHtmlState;
|
|
192
237
|
previousHtmlState = null;
|
|
@@ -199,6 +244,7 @@ function editAction(uuid) {
|
|
|
199
244
|
.then(html => {
|
|
200
245
|
document.getElementById('instrument-panel').innerHTML = html;
|
|
201
246
|
|
|
247
|
+
// Set up back button
|
|
202
248
|
const backButton = document.getElementById('back');
|
|
203
249
|
if (backButton) {
|
|
204
250
|
backButton.addEventListener('click', function(e) {
|
|
@@ -213,8 +259,53 @@ function editAction(uuid) {
|
|
|
213
259
|
.catch(error => console.error('Error:', error));
|
|
214
260
|
}
|
|
215
261
|
|
|
262
|
+
function submitEditForm(event) {
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
|
|
265
|
+
const form = event.target;
|
|
266
|
+
const formData = new FormData(form);
|
|
267
|
+
|
|
268
|
+
fetch(form.action, {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: formData
|
|
271
|
+
})
|
|
272
|
+
.then(response => response.text())
|
|
273
|
+
.then(html => {
|
|
274
|
+
if (html) {
|
|
275
|
+
updateActionCanvas(html);
|
|
276
|
+
|
|
277
|
+
// Restore previous instrument panel state
|
|
278
|
+
if (previousHtmlState) {
|
|
279
|
+
document.getElementById('instrument-panel').innerHTML = previousHtmlState;
|
|
280
|
+
previousHtmlState = null;
|
|
281
|
+
}
|
|
216
282
|
|
|
283
|
+
// Check for warnings
|
|
284
|
+
showWarningIfExists(html);
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
.catch(error => console.error('Error:', error));
|
|
288
|
+
}
|
|
217
289
|
|
|
290
|
+
function duplicateAction(uuid) {
|
|
291
|
+
if (!uuid) {
|
|
292
|
+
console.error('Invalid UUID');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fetch(scriptStepDupUrl.replace('0', uuid), {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: {
|
|
299
|
+
'Content-Type': 'application/json'
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
.then(response => response.text())
|
|
303
|
+
.then(html => {
|
|
304
|
+
updateActionCanvas(html);
|
|
305
|
+
showWarningIfExists(html);
|
|
306
|
+
})
|
|
307
|
+
.catch(error => console.error('Error:', error));
|
|
308
|
+
}
|
|
218
309
|
|
|
219
310
|
function deleteAction(uuid) {
|
|
220
311
|
if (!uuid) {
|
|
@@ -230,21 +321,64 @@ function deleteAction(uuid) {
|
|
|
230
321
|
})
|
|
231
322
|
.then(response => response.text())
|
|
232
323
|
.then(html => {
|
|
233
|
-
// Find the first list element's content and replace it
|
|
234
324
|
updateActionCanvas(html);
|
|
235
|
-
|
|
236
|
-
const parser = new DOMParser();
|
|
237
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
238
|
-
const warningDiv = doc.querySelector('#warning');
|
|
239
|
-
if (warningDiv && warningDiv.textContent.trim()) {
|
|
240
|
-
alert(warningDiv.textContent.trim()); // or use a nicer toast
|
|
241
|
-
}
|
|
325
|
+
showWarningIfExists(html);
|
|
242
326
|
})
|
|
243
327
|
.catch(error => console.error('Error:', error));
|
|
244
328
|
}
|
|
245
329
|
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// MODAL MANAGEMENT
|
|
332
|
+
// ============================================================================
|
|
333
|
+
|
|
334
|
+
function hideModal() {
|
|
335
|
+
if (document.activeElement) {
|
|
336
|
+
document.activeElement.blur();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
$('#dropModal').modal('hide');
|
|
340
|
+
|
|
341
|
+
if (lastFocusedElement) {
|
|
342
|
+
lastFocusedElement.focus();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// UTILITY FUNCTIONS
|
|
348
|
+
// ============================================================================
|
|
349
|
+
|
|
350
|
+
function showWarningIfExists(html) {
|
|
351
|
+
const parser = new DOMParser();
|
|
352
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
353
|
+
const warningDiv = doc.querySelector('#warning');
|
|
354
|
+
|
|
355
|
+
if (warningDiv && warningDiv.textContent.trim()) {
|
|
356
|
+
alert(warningDiv.textContent.trim());
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// INITIALIZATION
|
|
362
|
+
// ============================================================================
|
|
246
363
|
|
|
364
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
365
|
+
const mode = getMode();
|
|
366
|
+
const batch = getBatch();
|
|
247
367
|
|
|
368
|
+
// Set up mode/batch toggle listeners
|
|
369
|
+
document.querySelectorAll(".mode-toggle").forEach(btn => {
|
|
370
|
+
btn.addEventListener("click", () => setMode(btn.dataset.mode));
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
document.querySelectorAll(".batch-toggle").forEach(btn => {
|
|
374
|
+
btn.addEventListener("click", () => setBatch(btn.dataset.batch));
|
|
375
|
+
});
|
|
248
376
|
|
|
377
|
+
// Restore UI state (without triggering updates)
|
|
378
|
+
setMode(mode, false);
|
|
379
|
+
setBatch(batch, false);
|
|
249
380
|
|
|
381
|
+
// Initialize code overlay
|
|
382
|
+
initializeCodeOverlay();
|
|
250
383
|
|
|
384
|
+
});
|
ivoryos/utils/bo_campaign.py
CHANGED
|
@@ -134,7 +134,7 @@ def parse_optimization_form(form_data: Dict[str, str]):
|
|
|
134
134
|
Parse dynamic form data into structured optimization configuration.
|
|
135
135
|
|
|
136
136
|
Expected form field patterns:
|
|
137
|
-
- Objectives: {name}
|
|
137
|
+
- Objectives: {name}_obj_min, {name}_weight
|
|
138
138
|
- Parameters: {name}_type, {name}_min, {name}_max, {name}_choices, {name}_value_type
|
|
139
139
|
- Config: step{n}_model, step{n}_num_samples
|
|
140
140
|
"""
|
|
@@ -149,25 +149,25 @@ def parse_optimization_form(form_data: Dict[str, str]):
|
|
|
149
149
|
|
|
150
150
|
# Parse objectives
|
|
151
151
|
for field_name, value in form_data.items():
|
|
152
|
-
if field_name.endswith('
|
|
152
|
+
if field_name.endswith('_obj_min') and value:
|
|
153
153
|
# Extract objective name
|
|
154
|
-
obj_name = field_name.replace('
|
|
154
|
+
obj_name = field_name.replace('_obj_min', '')
|
|
155
155
|
if obj_name in processed_objectives:
|
|
156
156
|
continue
|
|
157
157
|
|
|
158
158
|
# Check if corresponding weight exists
|
|
159
159
|
weight_field = f"{obj_name}_weight"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
"minimize": value == "minimize",
|
|
164
|
-
"weight": float(form_data[weight_field])
|
|
165
|
-
})
|
|
166
|
-
else:
|
|
167
|
-
objectives.append({
|
|
160
|
+
early_stop_field = f"{obj_name}_obj_threshold"
|
|
161
|
+
|
|
162
|
+
config = {
|
|
168
163
|
"name": obj_name,
|
|
169
164
|
"minimize": value == "minimize",
|
|
170
|
-
}
|
|
165
|
+
}
|
|
166
|
+
if weight_field in form_data and form_data[weight_field]:
|
|
167
|
+
config["weight"] = float(form_data[weight_field])
|
|
168
|
+
if early_stop_field in form_data and form_data[early_stop_field]:
|
|
169
|
+
config["early_stop"] = float(form_data[early_stop_field])
|
|
170
|
+
objectives.append(config)
|
|
171
171
|
processed_objectives.add(obj_name)
|
|
172
172
|
|
|
173
173
|
# Parse parameters
|
|
@@ -192,11 +192,11 @@ def parse_optimization_form(form_data: Dict[str, str]):
|
|
|
192
192
|
if value == "range":
|
|
193
193
|
min_field = f"{param_name}_min"
|
|
194
194
|
max_field = f"{param_name}_max"
|
|
195
|
-
|
|
195
|
+
step_field = f"{param_name}_step"
|
|
196
196
|
if min_field in form_data and max_field in form_data:
|
|
197
197
|
min_val = form_data[min_field]
|
|
198
198
|
max_val = form_data[max_field]
|
|
199
|
-
|
|
199
|
+
step_val = form_data[step_field] if step_field in form_data else None
|
|
200
200
|
if min_val and max_val:
|
|
201
201
|
# Convert based on value_type
|
|
202
202
|
if value_type == "int":
|
|
@@ -204,8 +204,9 @@ def parse_optimization_form(form_data: Dict[str, str]):
|
|
|
204
204
|
elif value_type == "float":
|
|
205
205
|
bounds = [float(min_val), float(max_val)]
|
|
206
206
|
else: # string
|
|
207
|
-
bounds = [
|
|
208
|
-
|
|
207
|
+
bounds = [float(min_val), float(max_val)]
|
|
208
|
+
if step_val:
|
|
209
|
+
bounds.append(float(step_val))
|
|
209
210
|
parameter["bounds"] = bounds
|
|
210
211
|
|
|
211
212
|
elif value == "choice":
|