ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,16 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Move triggerModal to global scope
|
|
2
|
+
function triggerModal(formHtml, actionName, actionId, dropTargetId) {
|
|
3
|
+
if (formHtml && formHtml.trim() !== "") {
|
|
4
|
+
var $form = $("<div>").html(formHtml);
|
|
5
|
+
|
|
6
|
+
var $hiddenInput = $("<input>")
|
|
7
|
+
.attr("type", "hidden")
|
|
8
|
+
.attr("name", "drop_target_id")
|
|
9
|
+
.attr("id", "dropTargetInput")
|
|
10
|
+
.val(dropTargetId);
|
|
11
|
+
|
|
12
|
+
$form.find("button[type='submit']").before($hiddenInput);
|
|
13
|
+
|
|
14
|
+
$("#modalFormFields").empty().append($form.children());
|
|
15
|
+
|
|
16
|
+
const $modal = $("#dropModal");
|
|
17
|
+
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
showModal($modal);
|
|
20
|
+
}, 0);
|
|
21
|
+
|
|
22
|
+
$("#modalDropTarget").text(dropTargetId || "N/A");
|
|
23
|
+
$("#modalFormFields")
|
|
24
|
+
.data("action-id", actionId)
|
|
25
|
+
.data("action-name", actionName)
|
|
26
|
+
.data("drop-target-id", dropTargetId);
|
|
27
|
+
} else {
|
|
28
|
+
console.error("Form HTML is undefined or empty!");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function showModal($modal) {
|
|
33
|
+
$modal.modal({
|
|
34
|
+
backdrop: 'static',
|
|
35
|
+
keyboard: true,
|
|
36
|
+
focus: true
|
|
37
|
+
}).modal('show');
|
|
38
|
+
}
|
|
3
39
|
|
|
40
|
+
const state = {
|
|
41
|
+
dropTargetId: ""
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function initializeCanvas() {
|
|
4
45
|
$("#list ul").sortable({
|
|
5
46
|
cancel: ".unsortable",
|
|
6
47
|
opacity: 0.8,
|
|
7
48
|
cursor: "move",
|
|
8
49
|
placeholder: "drop-placeholder",
|
|
9
50
|
update: function () {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
51
|
+
const item_order = $("ul.reorder li").map(function () {
|
|
52
|
+
return this.id;
|
|
53
|
+
}).get();
|
|
14
54
|
var order_string = "order=" + item_order.join(",");
|
|
15
55
|
|
|
16
56
|
$.ajax({
|
|
@@ -19,87 +59,98 @@ $(document).ready(function () {
|
|
|
19
59
|
data: order_string,
|
|
20
60
|
cache: false,
|
|
21
61
|
success: function (data) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
window.location.href = window.location.href;
|
|
62
|
+
// Update the canvas content with the new HTML
|
|
63
|
+
updateActionCanvas(data)
|
|
25
64
|
}
|
|
65
|
+
}).fail(function (jqXHR, textStatus, errorThrown) {
|
|
66
|
+
console.error("Failed to update order:", textStatus, errorThrown);
|
|
26
67
|
});
|
|
27
68
|
}
|
|
28
69
|
});
|
|
29
70
|
|
|
30
71
|
// Make Entire Accordion Item Draggable
|
|
31
|
-
$(".accordion-item").on("dragstart", function (event) {
|
|
32
|
-
let formHtml = $(this).find(".accordion-body").html();
|
|
33
|
-
event.originalEvent.dataTransfer.setData("form", formHtml || "");
|
|
72
|
+
$(".accordion-item").off("dragstart").on("dragstart", function (event) {
|
|
73
|
+
let formHtml = $(this).find(".accordion-body").html();
|
|
74
|
+
event.originalEvent.dataTransfer.setData("form", formHtml || "");
|
|
34
75
|
event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
|
|
35
76
|
event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
|
|
36
|
-
|
|
37
77
|
$(this).addClass("dragging");
|
|
38
78
|
});
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
$("#list ul, .canvas").on("dragover", function (event) {
|
|
80
|
+
$("#list ul, .canvas").off("dragover").on("dragover", function (event) {
|
|
42
81
|
event.preventDefault();
|
|
43
82
|
let $target = $(event.target).closest("li");
|
|
44
83
|
|
|
45
|
-
// If we're over a valid <li> element in the list
|
|
46
84
|
if ($target.length) {
|
|
47
|
-
dropTargetId = $target.attr("id") || "";
|
|
48
|
-
|
|
49
|
-
$(".drop-placeholder").remove(); // Remove existing placeholders
|
|
50
|
-
$("<li class='drop-placeholder'></li>").insertBefore($target); // Insert before the target element
|
|
85
|
+
state.dropTargetId = $target.attr("id") || "";
|
|
86
|
+
insertDropPlaceholder($target);
|
|
51
87
|
} else if (!$("#list ul").children().length && $(this).hasClass("canvas")) {
|
|
52
|
-
$(".drop-placeholder").remove();
|
|
53
|
-
// $("#list ul").append("<li class='drop-placeholder'></li>"); // Append placeholder to canvas
|
|
88
|
+
$(".drop-placeholder").remove();
|
|
54
89
|
} else {
|
|
55
|
-
dropTargetId = "";
|
|
90
|
+
state.dropTargetId = "";
|
|
56
91
|
}
|
|
57
92
|
});
|
|
58
93
|
|
|
59
|
-
$("#list ul, .canvas").on("dragleave", function () {
|
|
60
|
-
$(".drop-placeholder").remove();
|
|
94
|
+
$("#list ul, .canvas").off("dragleave").on("dragleave", function () {
|
|
95
|
+
$(".drop-placeholder").remove();
|
|
61
96
|
});
|
|
62
97
|
|
|
63
|
-
$("#list ul, .canvas").on("drop", function (event) {
|
|
98
|
+
$("#list ul, .canvas").off("drop").on("drop", function (event) {
|
|
64
99
|
event.preventDefault();
|
|
65
|
-
|
|
66
100
|
var actionName = event.originalEvent.dataTransfer.getData("action");
|
|
67
101
|
var actionId = event.originalEvent.dataTransfer.getData("id");
|
|
68
|
-
var formHtml = event.originalEvent.dataTransfer.getData("form");
|
|
102
|
+
var formHtml = event.originalEvent.dataTransfer.getData("form");
|
|
69
103
|
let listLength = $("ul.reorder li").length;
|
|
70
|
-
dropTargetId = dropTargetId || listLength + 1;
|
|
104
|
+
state.dropTargetId = state.dropTargetId || listLength + 1;
|
|
71
105
|
$(".drop-placeholder").remove();
|
|
72
|
-
|
|
73
|
-
triggerModal(formHtml, actionName, actionId, dropTargetId);
|
|
74
|
-
|
|
106
|
+
document.activeElement?.blur();
|
|
107
|
+
triggerModal(formHtml, actionName, actionId, state.dropTargetId);
|
|
75
108
|
});
|
|
109
|
+
initializeCodeOverlay();
|
|
110
|
+
}
|
|
76
111
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
function insertDropPlaceholder($target) {
|
|
113
|
+
$(".drop-placeholder").remove();
|
|
114
|
+
$("<li class='drop-placeholder'></li>").insertBefore($target);
|
|
115
|
+
}
|
|
81
116
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.attr("name", "drop_target_id")
|
|
86
|
-
.attr("id", "dropTargetInput")
|
|
87
|
-
.val(dropTargetId);
|
|
117
|
+
// Add this function to sortable_design.js
|
|
118
|
+
function initializeDragHandlers() {
|
|
119
|
+
const $cards = $(".accordion-item.design-control");
|
|
88
120
|
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
// Toggle draggable based on mouse/touch position
|
|
122
|
+
$cards.off("mousedown touchstart").on("mousedown touchstart", function (event) {
|
|
123
|
+
this.setAttribute("draggable", $(event.target).closest(".input-group").length ? "false" : "true");
|
|
124
|
+
});
|
|
91
125
|
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
// Handle the actual drag
|
|
127
|
+
$cards.off("dragstart dragend").on({
|
|
128
|
+
dragstart: function (event) {
|
|
129
|
+
if (this.getAttribute("draggable") !== "true") {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const formHtml = $(this).find(".accordion-body form").prop("outerHTML");
|
|
135
|
+
if (!formHtml) return false;
|
|
136
|
+
|
|
137
|
+
event.originalEvent.dataTransfer.setData("form", formHtml);
|
|
138
|
+
event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
|
|
139
|
+
event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
|
|
140
|
+
|
|
141
|
+
$(this).addClass("dragging");
|
|
142
|
+
},
|
|
143
|
+
dragend: function () {
|
|
144
|
+
$(this).removeClass("dragging").attr("draggable", "false");
|
|
145
|
+
}
|
|
146
|
+
});
|
|
94
147
|
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
// Prevent form inputs from being draggable
|
|
149
|
+
$(".accordion-item input, .accordion-item select").attr("draggable", "false");
|
|
150
|
+
}
|
|
97
151
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
152
|
+
// Make sure it's called in the document ready function
|
|
153
|
+
$(document).ready(function () {
|
|
154
|
+
initializeCanvas();
|
|
155
|
+
initializeDragHandlers(); // Add this line
|
|
156
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Toggle visibility of line numbers
|
|
2
|
+
function toggleLineNumbers(save = true) {
|
|
3
|
+
const show = document.getElementById('toggleLineNumbers').checked;
|
|
4
|
+
document.querySelectorAll('.line-number').forEach(el => {
|
|
5
|
+
el.classList.toggle('d-none', !show);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
if (save) {
|
|
9
|
+
localStorage.setItem('showLineNumbers', show ? 'true' : 'false');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Toggle visibility of Python code overlay
|
|
14
|
+
function toggleCodeOverlay(state = null) {
|
|
15
|
+
const overlay = document.getElementById("pythonCodeOverlay");
|
|
16
|
+
const checkbox = document.getElementById("showPythonCodeSwitch");
|
|
17
|
+
|
|
18
|
+
const isVisible = overlay.classList.contains("show");
|
|
19
|
+
const newState = state !== null ? state : !isVisible;
|
|
20
|
+
|
|
21
|
+
if (newState) {
|
|
22
|
+
overlay.classList.add("show");
|
|
23
|
+
checkbox.checked = true;
|
|
24
|
+
} else {
|
|
25
|
+
overlay.classList.remove("show");
|
|
26
|
+
checkbox.checked = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Save state to session via PATCH
|
|
30
|
+
fetch(scriptUIStateUrl, {
|
|
31
|
+
method: "PATCH",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify({ show_code: newState })
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function setScriptPhase(stype) {
|
|
39
|
+
fetch(scriptUIStateUrl, {
|
|
40
|
+
method: "PATCH",
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({ editing_type: stype })
|
|
45
|
+
})
|
|
46
|
+
.then(res => res.json())
|
|
47
|
+
.then(data => {
|
|
48
|
+
if (data.html) {
|
|
49
|
+
document.getElementById("canvas-wrapper").innerHTML = data.html;
|
|
50
|
+
initializeCanvas(); // Reinitialize the canvas functionality
|
|
51
|
+
document.querySelectorAll('#pythonCodeOverlay pre code').forEach((block) => {
|
|
52
|
+
hljs.highlightElement(block);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch(error => console.error("Failed to update editing type", error));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function changeDeck(deck) {
|
|
62
|
+
fetch(scriptUIStateUrl, {
|
|
63
|
+
method: "PATCH",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({ deck_name: deck })
|
|
68
|
+
})
|
|
69
|
+
.then(res => res.json())
|
|
70
|
+
.then(data => {
|
|
71
|
+
if (data.html) {
|
|
72
|
+
document.getElementById("sidebar-wrapper").innerHTML = data.html;
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.catch(error => console.error("Failed to change deck", error));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function toggleAutoFill() {
|
|
81
|
+
const instrumentValue = document.querySelector('.form-check.form-switch').dataset.instrument;
|
|
82
|
+
|
|
83
|
+
fetch(scriptUIStateUrl, {
|
|
84
|
+
method: "PATCH",
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
autofill: document.getElementById("autoFillCheck").checked,
|
|
90
|
+
instrument: instrumentValue
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
.then(res => res.json())
|
|
94
|
+
.then(data => {
|
|
95
|
+
if (data.html) {
|
|
96
|
+
document.getElementById("instrument-panel").innerHTML = data.html;
|
|
97
|
+
initializeDragHandlers()
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
// Restore state on page load
|
|
102
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
103
|
+
const savedState = localStorage.getItem('showLineNumbers');
|
|
104
|
+
const checkbox = document.getElementById('toggleLineNumbers');
|
|
105
|
+
|
|
106
|
+
if (savedState === 'true') {
|
|
107
|
+
checkbox.checked = true;
|
|
108
|
+
}
|
|
109
|
+
if (checkbox) {
|
|
110
|
+
toggleLineNumbers(false); // don't overwrite localStorage on load
|
|
111
|
+
checkbox.addEventListener('change', () => toggleLineNumbers());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
});
|
ivoryos/templates/base.html
CHANGED
|
@@ -24,9 +24,25 @@
|
|
|
24
24
|
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
|
|
25
25
|
<div class= "container">
|
|
26
26
|
{# {{ module_config }}#}
|
|
27
|
+
|
|
27
28
|
<a class="navbar-brand" href="{{ url_for('main.index') }}">
|
|
28
|
-
|
|
29
|
+
{% if current_user.is_authenticated %}
|
|
30
|
+
{% if current_user.settings.logo_mode == 'replace' and current_user.settings.logo_filename %}
|
|
31
|
+
<img src="{{ url_for('static', filename='user_logos/' ~ current_user.settings.logo_filename) }}" alt="User Logo" height="50">
|
|
32
|
+
{% else %}
|
|
33
|
+
|
|
34
|
+
{% if current_user.settings.logo_mode == 'add' and current_user.settings.logo_filename %}
|
|
35
|
+
<img src="{{ url_for('static', filename='user_logos/' ~ current_user.settings.logo_filename) }}" alt="User Logo" height="50" class="ms-2">
|
|
36
|
+
{% endif %}
|
|
37
|
+
<img src="{{ url_for('static', filename='ivoryos_logo.png') }}" alt="IvoryOS" height="50">
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% else %}
|
|
40
|
+
<img src="{{ url_for('static', filename='ivoryos_logo.png') }}" alt="IvoryOS" height="50">
|
|
41
|
+
{% endif %}
|
|
29
42
|
</a>
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
30
46
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
31
47
|
<span class="navbar-toggler-icon"></span>
|
|
32
48
|
</button>
|
|
@@ -38,16 +54,16 @@
|
|
|
38
54
|
</li>
|
|
39
55
|
{% if enable_design %}
|
|
40
56
|
<li class="nav-item">
|
|
41
|
-
<a class="nav-link" href="{{ url_for('
|
|
57
|
+
<a class="nav-link" href="{{ url_for('library.load_from_database') }}" aria-current="page">Library</a>
|
|
42
58
|
</li>
|
|
43
59
|
<li class="nav-item">
|
|
44
60
|
<a class="nav-link" href="{{ url_for('design.experiment_builder') }}">Design</a>
|
|
45
61
|
</li>
|
|
46
62
|
<li class="nav-item">
|
|
47
|
-
<a class="nav-link" href="{{ url_for('
|
|
63
|
+
<a class="nav-link" href="{{ url_for('execute.experiment_run') }}">Compile/Run</a>
|
|
48
64
|
</li>
|
|
49
65
|
<li class="nav-item">
|
|
50
|
-
<a class="nav-link" href="{{ url_for('
|
|
66
|
+
<a class="nav-link" href="{{ url_for('data.list_workflows') }}">Data</a>
|
|
51
67
|
</li>
|
|
52
68
|
{% endif %}
|
|
53
69
|
|
|
@@ -69,11 +85,12 @@
|
|
|
69
85
|
{% endif %}
|
|
70
86
|
</ul>
|
|
71
87
|
<ul class="navbar-nav ms-auto">
|
|
72
|
-
|
|
73
|
-
{% if session["user"] %}
|
|
88
|
+
{% if current_user.is_authenticated %}
|
|
74
89
|
<div class="dropdown">
|
|
75
|
-
<li class="nav-item " aria-expanded="false"><i class="bi bi-person-circle"></i> {{
|
|
90
|
+
<li class="nav-item " aria-expanded="false"><i class="bi bi-person-circle"></i> {{ current_user.get_id() }}</li>
|
|
76
91
|
<ul class="dropdown-menu">
|
|
92
|
+
<li><a class="dropdown-item" href="{{ url_for("auth.change_password") }}" role="button" aria-expanded="false">Change Password</a></li>
|
|
93
|
+
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#logoModal" role="button" aria-expanded="false">Customize Logo</a></li>
|
|
77
94
|
<li><a class="dropdown-item" href="{{ url_for("auth.logout") }}" role="button" aria-expanded="false">Logout</a></li>
|
|
78
95
|
</ul>
|
|
79
96
|
|
|
@@ -116,7 +133,7 @@
|
|
|
116
133
|
<h1 class="modal-title fs-5" id="importModal">Import deck by file path</h1>
|
|
117
134
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
118
135
|
</div>
|
|
119
|
-
<form method="POST" action="{{ url_for('control.import_deck') }}" enctype="multipart/form-data">
|
|
136
|
+
<form method="POST" action="{{ url_for('control.temp.import_deck') }}" enctype="multipart/form-data">
|
|
120
137
|
<div class="modal-body">
|
|
121
138
|
<h5>from connection history</h5>
|
|
122
139
|
<div class="form-group">
|
|
@@ -153,5 +170,47 @@
|
|
|
153
170
|
</div>
|
|
154
171
|
</div>
|
|
155
172
|
</div>
|
|
173
|
+
|
|
174
|
+
<!-- Logo Customization Modal -->
|
|
175
|
+
<div class="modal fade" id="logoModal" tabindex="-1" aria-labelledby="logoModalLabel" aria-hidden="true">
|
|
176
|
+
<div class="modal-dialog modal-dialog-centered">
|
|
177
|
+
<div class="modal-content shadow-lg">
|
|
178
|
+
<div class="modal-header">
|
|
179
|
+
<h5 class="modal-title" id="logoModalLabel">Customize Navbar Logo</h5>
|
|
180
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
181
|
+
</div>
|
|
182
|
+
<form method="POST"
|
|
183
|
+
action="{{ url_for('main.customize_logo') }}"
|
|
184
|
+
enctype="multipart/form-data">
|
|
185
|
+
<div class="modal-body">
|
|
186
|
+
<div class="mb-3">
|
|
187
|
+
<label class="form-label fw-semibold">Upload your logo</label>
|
|
188
|
+
<input type="file" name="logo" accept="image/*" class="form-control" required>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="mb-2">
|
|
191
|
+
<label class="form-label fw-semibold">Display mode</label>
|
|
192
|
+
<div class="form-check">
|
|
193
|
+
<input class="form-check-input" type="radio" name="mode" id="add" value="add" checked>
|
|
194
|
+
<label class="form-check-label" for="add">
|
|
195
|
+
Add next to IvoryOS logo
|
|
196
|
+
</label>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="form-check">
|
|
199
|
+
<input class="form-check-input" type="radio" name="mode" id="replace" value="replace">
|
|
200
|
+
<label class="form-check-label" for="replace">
|
|
201
|
+
Replace IvoryOS logo
|
|
202
|
+
</label>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="modal-footer">
|
|
207
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
208
|
+
<button type="submit" class="btn btn-primary">Apply</button>
|
|
209
|
+
</div>
|
|
210
|
+
</form>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
156
215
|
</body>
|
|
157
216
|
</html>
|
ivoryos/utils/bo_campaign.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import re
|
|
1
3
|
from ivoryos.utils.utils import install_and_import
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def ax_init_form(data, arg_types):
|
|
6
|
+
def ax_init_form(data, arg_types, previous_data_len=0):
|
|
5
7
|
"""
|
|
6
8
|
create Ax campaign from the web form input
|
|
7
9
|
:param data:
|
|
@@ -9,7 +11,11 @@ def ax_init_form(data, arg_types):
|
|
|
9
11
|
install_and_import("ax", "ax-platform")
|
|
10
12
|
parameter, objectives = ax_wrapper(data, arg_types)
|
|
11
13
|
from ax.service.ax_client import AxClient
|
|
12
|
-
|
|
14
|
+
if previous_data_len > 0:
|
|
15
|
+
gs = exisitng_data_gs(previous_data_len)
|
|
16
|
+
ax_client = AxClient(generation_strategy=gs)
|
|
17
|
+
else:
|
|
18
|
+
ax_client = AxClient()
|
|
13
19
|
ax_client.create_experiment(parameter, objectives=objectives)
|
|
14
20
|
return ax_client
|
|
15
21
|
|
|
@@ -84,4 +90,175 @@ def ax_init_opc(bo_args):
|
|
|
84
90
|
bo_args["objectives"] = objectives_formatted
|
|
85
91
|
ax_client.create_experiment(**bo_args)
|
|
86
92
|
|
|
87
|
-
return ax_client
|
|
93
|
+
return ax_client
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def exisitng_data_gs(data_len):
|
|
97
|
+
"""
|
|
98
|
+
temporal generation strategy for existing data
|
|
99
|
+
"""
|
|
100
|
+
from ax.generation_strategy.generation_node import GenerationStep
|
|
101
|
+
from ax.generation_strategy.generation_strategy import GenerationStrategy
|
|
102
|
+
from ax.modelbridge.registry import Generators
|
|
103
|
+
if data_len > 4:
|
|
104
|
+
gs = GenerationStrategy(
|
|
105
|
+
steps=[
|
|
106
|
+
GenerationStep(
|
|
107
|
+
model=Generators.BOTORCH_MODULAR,
|
|
108
|
+
num_trials=-1,
|
|
109
|
+
max_parallelism=3,
|
|
110
|
+
),
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
gs = GenerationStrategy(
|
|
115
|
+
steps=[
|
|
116
|
+
GenerationStep(
|
|
117
|
+
model=Generators.SOBOL,
|
|
118
|
+
num_trials=5-data_len, # how many sobol trials to perform (rule of thumb: 2 * number of params)
|
|
119
|
+
max_parallelism=5,
|
|
120
|
+
model_kwargs={"seed": 999},
|
|
121
|
+
),
|
|
122
|
+
GenerationStep(
|
|
123
|
+
model=Generators.BOTORCH_MODULAR,
|
|
124
|
+
num_trials=-1,
|
|
125
|
+
max_parallelism=3,
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
return gs
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse_optimization_form(form_data: Dict[str, str]):
|
|
133
|
+
"""
|
|
134
|
+
Parse dynamic form data into structured optimization configuration.
|
|
135
|
+
|
|
136
|
+
Expected form field patterns:
|
|
137
|
+
- Objectives: {name}_obj_min, {name}_weight
|
|
138
|
+
- Parameters: {name}_type, {name}_min, {name}_max, {name}_choices, {name}_value_type
|
|
139
|
+
- Config: step{n}_model, step{n}_num_samples
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
objectives = []
|
|
143
|
+
parameters = []
|
|
144
|
+
config = {}
|
|
145
|
+
|
|
146
|
+
# Track processed field names to avoid duplicates
|
|
147
|
+
processed_objectives = set()
|
|
148
|
+
processed_parameters = set()
|
|
149
|
+
|
|
150
|
+
# Parse objectives
|
|
151
|
+
for field_name, value in form_data.items():
|
|
152
|
+
if field_name.endswith('_obj_min') and value:
|
|
153
|
+
# Extract objective name
|
|
154
|
+
obj_name = field_name.replace('_obj_min', '')
|
|
155
|
+
if obj_name in processed_objectives:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Check if corresponding weight exists
|
|
159
|
+
weight_field = f"{obj_name}_weight"
|
|
160
|
+
early_stop_field = f"{obj_name}_obj_threshold"
|
|
161
|
+
|
|
162
|
+
config = {
|
|
163
|
+
"name": obj_name,
|
|
164
|
+
"minimize": value == "minimize",
|
|
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
|
+
processed_objectives.add(obj_name)
|
|
172
|
+
|
|
173
|
+
# Parse parameters
|
|
174
|
+
for field_name, value in form_data.items():
|
|
175
|
+
if field_name.endswith('_type') and value:
|
|
176
|
+
# Extract parameter name
|
|
177
|
+
param_name = field_name.replace('_type', '')
|
|
178
|
+
if param_name in processed_parameters:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
parameter = {
|
|
182
|
+
"name": param_name,
|
|
183
|
+
"type": value
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Get value type (default to float)
|
|
187
|
+
value_type_field = f"{param_name}_value_type"
|
|
188
|
+
value_type = form_data.get(value_type_field, "float")
|
|
189
|
+
parameter["value_type"] = value_type
|
|
190
|
+
|
|
191
|
+
# Handle different parameter types
|
|
192
|
+
if value == "range":
|
|
193
|
+
min_field = f"{param_name}_min"
|
|
194
|
+
max_field = f"{param_name}_max"
|
|
195
|
+
step_field = f"{param_name}_step"
|
|
196
|
+
if min_field in form_data and max_field in form_data:
|
|
197
|
+
min_val = form_data[min_field]
|
|
198
|
+
max_val = form_data[max_field]
|
|
199
|
+
step_val = form_data[step_field] if step_field in form_data else None
|
|
200
|
+
if min_val and max_val:
|
|
201
|
+
# Convert based on value_type
|
|
202
|
+
if value_type == "int":
|
|
203
|
+
bounds = [int(min_val), int(max_val)]
|
|
204
|
+
elif value_type == "float":
|
|
205
|
+
bounds = [float(min_val), float(max_val)]
|
|
206
|
+
else: # string
|
|
207
|
+
bounds = [float(min_val), float(max_val)]
|
|
208
|
+
if step_val:
|
|
209
|
+
bounds.append(float(step_val))
|
|
210
|
+
parameter["bounds"] = bounds
|
|
211
|
+
|
|
212
|
+
elif value == "choice":
|
|
213
|
+
choices_field = f"{param_name}_value"
|
|
214
|
+
if choices_field in form_data and form_data[choices_field]:
|
|
215
|
+
# Split choices by comma and clean whitespace
|
|
216
|
+
choices = [choice.strip() for choice in form_data[choices_field].split(',')]
|
|
217
|
+
|
|
218
|
+
# Convert choices based on value_type
|
|
219
|
+
if value_type == "int":
|
|
220
|
+
choices = [int(choice) for choice in choices if choice.isdigit()]
|
|
221
|
+
elif value_type == "float":
|
|
222
|
+
choices = [float(choice) for choice in choices if
|
|
223
|
+
choice.replace('.', '').replace('-', '').isdigit()]
|
|
224
|
+
# For string, keep as is
|
|
225
|
+
|
|
226
|
+
parameter["bounds"] = choices
|
|
227
|
+
|
|
228
|
+
elif value == "fixed":
|
|
229
|
+
fixed_field = f"{param_name}_value"
|
|
230
|
+
if fixed_field in form_data and form_data[fixed_field]:
|
|
231
|
+
fixed_val = form_data[fixed_field]
|
|
232
|
+
|
|
233
|
+
# Convert based on value_type
|
|
234
|
+
if value_type == "int":
|
|
235
|
+
parameter["value"] = int(fixed_val)
|
|
236
|
+
elif value_type == "float":
|
|
237
|
+
parameter["value"] = float(fixed_val)
|
|
238
|
+
else:
|
|
239
|
+
parameter["value"] = str(fixed_val)
|
|
240
|
+
|
|
241
|
+
parameters.append(parameter)
|
|
242
|
+
processed_parameters.add(param_name)
|
|
243
|
+
|
|
244
|
+
# Parse configuration steps
|
|
245
|
+
step_pattern = re.compile(r'step(\d+)_(.+)')
|
|
246
|
+
steps = {}
|
|
247
|
+
|
|
248
|
+
for field_name, value in form_data.items():
|
|
249
|
+
match = step_pattern.match(field_name)
|
|
250
|
+
if match and value:
|
|
251
|
+
step_num = int(match.group(1))
|
|
252
|
+
step_attr = match.group(2)
|
|
253
|
+
step_key = f"step_{step_num}"
|
|
254
|
+
|
|
255
|
+
if step_key not in steps:
|
|
256
|
+
steps[step_key] = {}
|
|
257
|
+
|
|
258
|
+
# Convert num_samples to int if it's a number field
|
|
259
|
+
if step_attr == "num_samples":
|
|
260
|
+
steps[step_key][step_attr] = int(value)
|
|
261
|
+
else:
|
|
262
|
+
steps[step_key][step_attr] = value
|
|
263
|
+
|
|
264
|
+
return parameters, objectives, steps
|