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.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
@@ -1,16 +1,56 @@
1
- $(document).ready(function () {
2
- let dropTargetId = ""; // Store the ID of the drop target
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
- var item_order = [];
11
- $("ul.reorder li").each(function () {
12
- item_order.push($(this).attr("id"));
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
- $("#response").html(data);
23
- $("#response").slideDown("slow");
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(); // Get the correct form
33
- event.originalEvent.dataTransfer.setData("form", formHtml || ""); // Store form HTML
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") || ""; // Store the drop target 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(); // Remove any placeholder
53
- // $("#list ul").append("<li class='drop-placeholder'></li>"); // Append placeholder to canvas
88
+ $(".drop-placeholder").remove();
54
89
  } else {
55
- dropTargetId = ""; // Append placeholder to canvas
90
+ state.dropTargetId = "";
56
91
  }
57
92
  });
58
93
 
59
- $("#list ul, .canvas").on("dragleave", function () {
60
- $(".drop-placeholder").remove(); // Remove placeholder on leave
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"); // Retrieve form HTML
102
+ var formHtml = event.originalEvent.dataTransfer.getData("form");
69
103
  let listLength = $("ul.reorder li").length;
70
- dropTargetId = dropTargetId || listLength + 1; // Assign a "last" ID or unique identifier
104
+ state.dropTargetId = state.dropTargetId || listLength + 1;
71
105
  $(".drop-placeholder").remove();
72
- // Trigger the modal with the appropriate action
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
- // Function to trigger the modal (same for both buttons and accordion items)
78
- function triggerModal(formHtml, actionName, actionId, dropTargetId) {
79
- if (formHtml && formHtml.trim() !== "") {
80
- var $form = $("<div>").html(formHtml); // Convert HTML string to jQuery object
112
+ function insertDropPlaceholder($target) {
113
+ $(".drop-placeholder").remove();
114
+ $("<li class='drop-placeholder'></li>").insertBefore($target);
115
+ }
81
116
 
82
- // Create a hidden input for the drop target ID
83
- var $hiddenInput = $("<input>")
84
- .attr("type", "hidden")
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
- // Insert before the submit button
90
- $form.find("button[type='submit']").before($hiddenInput);
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
- $("#modalFormFields").empty().append($form.children());
93
- $("#dropModal").modal("show"); // Show modal
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
- // Store and display drop target ID in the modal
96
- $("#modalDropTarget").text(dropTargetId || "N/A");
148
+ // Prevent form inputs from being draggable
149
+ $(".accordion-item input, .accordion-item select").attr("draggable", "false");
150
+ }
97
151
 
98
- $("#modalFormFields").data("action-id", actionId);
99
- $("#modalFormFields").data("action-name", actionName);
100
- $("#modalFormFields").data("drop-target-id", dropTargetId);
101
- } else {
102
- console.error("Form HTML is undefined or empty!");
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
+ });
@@ -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
- <img src="{{url_for('static', filename='logo.webp')}}" alt="Logo" height="60" class="d-inline-block align-text-bottom">
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('database.load_from_database') }}" aria-current="page">Library</a>
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('design.experiment_run') }}">Compile/Run</a>
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('database.list_workflows') }}">Data</a>
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> {{ session["user"] }}</li>
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>
@@ -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
- ax_client = AxClient()
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