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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!-- Main template structure -->
|
|
2
|
+
<ul class="nav nav-tabs" id="myTabs" role="tablist">
|
|
3
|
+
<li class="nav-item" role="presentation">
|
|
4
|
+
<a class="nav-link {{ 'disabled' if config_list else '' }} {{ 'active' if not config_list else '' }}" id="tab1-tab" data-bs-toggle="tab" href="#tab1" role="tab" aria-controls="tab1" aria-selected="false">Repeat</a>
|
|
5
|
+
</li>
|
|
6
|
+
<li class="nav-item" role="presentation">
|
|
7
|
+
<a class="nav-link {{ 'disabled' if not config_list else '' }} {{ 'active' if config_list else '' }}" id="tab2-tab" data-bs-toggle="tab" href="#tab2" role="tab" aria-controls="tab2" aria-selected="false">Configuration</a>
|
|
8
|
+
</li>
|
|
9
|
+
<li class="nav-item" role="presentation">
|
|
10
|
+
<a class="nav-link {{ 'disabled' if not config_list or not return_list else '' }}" id="tab3-tab" data-bs-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Bayesian Optimization</a>
|
|
11
|
+
</li>
|
|
12
|
+
</ul>
|
|
13
|
+
<div class="tab-content" id="myTabsContent">
|
|
14
|
+
{% include 'components/tab_repeat.html' %}
|
|
15
|
+
{% include 'components/tab_configuration.html' %}
|
|
16
|
+
{% include 'components/tab_bayesian.html' %}
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- ============================================ -->
|
|
20
|
+
<!-- SWITCH TO THE LAST ACTIVE TAB ON PAGE LOAD -->
|
|
21
|
+
<!-- ============================================ -->
|
|
22
|
+
<script>
|
|
23
|
+
(function() {
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// Store active tab when changed
|
|
27
|
+
document.addEventListener('shown.bs.tab', function(e) {
|
|
28
|
+
const tabId = e.target.id.replace('-tab', '');
|
|
29
|
+
if (tabId === 'tab2' || tabId === 'tab3') {
|
|
30
|
+
localStorage.setItem('ivoryosLastTab', tabId);
|
|
31
|
+
console.log('Saved last active tab:', tabId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Optional: run specific load logic per tab
|
|
35
|
+
if (tabId === 'tab2' && typeof window.loadConfigData === 'function') {
|
|
36
|
+
window.loadConfigData();
|
|
37
|
+
} else if (tabId === 'tab3' && typeof window.saveBO === 'function') {
|
|
38
|
+
window.saveBO();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Restore tab from last session
|
|
43
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
44
|
+
const lastTab = localStorage.getItem('ivoryosLastTab');
|
|
45
|
+
const tabElement = lastTab ? document.getElementById(lastTab + '-tab') : null;
|
|
46
|
+
|
|
47
|
+
if (tabElement && !tabElement.classList.contains('disabled')) {
|
|
48
|
+
new bootstrap.Tab(tabElement).show();
|
|
49
|
+
console.log('Restored last active tab:', lastTab);
|
|
50
|
+
} else {
|
|
51
|
+
// Default to first non-disabled tab
|
|
52
|
+
const firstTab = document.querySelector('#myTabs .nav-link:not(.disabled)');
|
|
53
|
+
if (firstTab) {
|
|
54
|
+
new bootstrap.Tab(firstTab).show();
|
|
55
|
+
console.log('Defaulted to first available tab.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
})();
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
{# Bayesian optimization tab component #}
|
|
2
|
+
|
|
3
|
+
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
|
|
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>
|
|
20
|
+
|
|
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>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
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)">
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
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>
|
|
123
|
+
</div>
|
|
124
|
+
{% endfor %}
|
|
125
|
+
|
|
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">
|
|
133
|
+
</div>
|
|
134
|
+
<div class="col-2">
|
|
135
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addConstraintRow()">+</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
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>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
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;">
|
|
157
|
+
|
|
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>
|
|
173
|
+
</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:
|
|
177
|
+
</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="">
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
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>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div id="optimizer_config_placeholder">
|
|
205
|
+
<small class="text-muted">Select an optimizer to configure advanced settings</small>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
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>';
|
|
253
|
+
});
|
|
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
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
function updateOptimizerConfig(config) {
|
|
266
|
+
const container = document.getElementById('optimizer_config_container');
|
|
267
|
+
const placeholder = document.getElementById('optimizer_config_placeholder');
|
|
268
|
+
|
|
269
|
+
console.log('Updating optimizer config:', config);
|
|
270
|
+
|
|
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;
|
|
276
|
+
}
|
|
277
|
+
|
|
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
|
+
}
|
|
292
|
+
|
|
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
|
+
}
|
|
304
|
+
|
|
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 => {
|
|
311
|
+
const option = document.createElement('option');
|
|
312
|
+
option.value = model;
|
|
313
|
+
option.textContent = model;
|
|
314
|
+
step2ModelSelect.appendChild(option);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
321
|
+
console.log("Tab3: Init");
|
|
322
|
+
|
|
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;
|
|
327
|
+
|
|
328
|
+
const saveFormData = () => {
|
|
329
|
+
if (!form) return;
|
|
330
|
+
|
|
331
|
+
clearTimeout(saveTimeout);
|
|
332
|
+
saveTimeout = setTimeout(() => {
|
|
333
|
+
const formData = new FormData(form);
|
|
334
|
+
const data = {};
|
|
335
|
+
|
|
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;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log('Tab3: Restoring form state');
|
|
372
|
+
|
|
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
|
+
}
|
|
383
|
+
|
|
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';
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const schema = optimizerSchema[opt];
|
|
407
|
+
if (!schema) return;
|
|
408
|
+
|
|
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);
|
|
414
|
+
}
|
|
415
|
+
|
|
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;
|
|
426
|
+
} else {
|
|
427
|
+
sel.value = available.includes("range") ? "range" : available[0];
|
|
428
|
+
}
|
|
429
|
+
|
|
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)";
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// Bind events
|
|
463
|
+
if (optimizerSelect) {
|
|
464
|
+
optimizerSelect.addEventListener("change", () => {
|
|
465
|
+
updateOptimizerInfo();
|
|
466
|
+
saveFormData();
|
|
467
|
+
});
|
|
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>
|