ivoryos 0.1.18__tar.gz → 0.1.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- {ivoryos-0.1.18/ivoryos.egg-info → ivoryos-0.1.20}/PKG-INFO +12 -4
- {ivoryos-0.1.18 → ivoryos-0.1.20}/README.md +11 -3
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/design.py +24 -8
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_run.html +49 -6
- ivoryos-0.1.20/ivoryos/static/js/socket_handler.js +72 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/db_models.py +27 -42
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/script_runner.py +91 -40
- ivoryos-0.1.20/ivoryos/version.py +1 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20/ivoryos.egg-info}/PKG-INFO +12 -4
- ivoryos-0.1.18/ivoryos/static/js/socket_handler.js +0 -34
- ivoryos-0.1.18/ivoryos/version.py +0 -1
- {ivoryos-0.1.18 → ivoryos-0.1.20}/LICENSE +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/MANIFEST.in +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/config.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/auth/templates/auth/login.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/control.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/database/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/database/database.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_builder.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/main/templates/main/help.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/main/templates/main/home.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/js/sortable_design.js +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/logo.webp +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/static/style.css +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/templates/base.html +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/form.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/global_config.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/utils/utils.py +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos.egg-info/SOURCES.txt +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/setup.cfg +0 -0
- {ivoryos-0.1.18 → ivoryos-0.1.20}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
6
|
Author: Ivory Zhang
|
|
@@ -13,6 +13,9 @@ License-File: LICENSE
|
|
|
13
13
|
[](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
|
|
14
14
|
[](https://pypi.org/project/ivoryos/)
|
|
15
15
|

|
|
16
|
+
[](https://youtu.be/dFfJv9I2-1g)
|
|
17
|
+
[](https://www.researchsquare.com/article/rs-5307798/v1)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|

|
|
18
21
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -24,6 +27,7 @@ License-File: LICENSE
|
|
|
24
27
|
- [Installation](#installation)
|
|
25
28
|
- [Instructions for use](#instructions-for-use)
|
|
26
29
|
- [Demo](#demo)
|
|
30
|
+
- [Roadmap](#roadmap)
|
|
27
31
|
- [License](#license)
|
|
28
32
|
|
|
29
33
|
## Description
|
|
@@ -152,9 +156,13 @@ When you run the application for the first time, it will automatically create th
|
|
|
152
156
|
- **`ivoryos.db`**: Database file that stores application data locally.
|
|
153
157
|
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
## Roadmap
|
|
160
|
+
|
|
161
|
+
- [x] Allow plugin pages ✅
|
|
162
|
+
- [x] pause, resume, abort current and pending workflows ✅
|
|
163
|
+
- [ ] snapshot version control
|
|
164
|
+
- [ ] dropdown input
|
|
165
|
+
- [ ] show line number option
|
|
158
166
|
|
|
159
167
|
|
|
160
168
|
## Authors and Acknowledgement
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
[](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
|
|
2
2
|
[](https://pypi.org/project/ivoryos/)
|
|
3
3
|

|
|
4
|
+
[](https://youtu.be/dFfJv9I2-1g)
|
|
5
|
+
[](https://www.researchsquare.com/article/rs-5307798/v1)
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|

|
|
6
9
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -12,6 +15,7 @@
|
|
|
12
15
|
- [Installation](#installation)
|
|
13
16
|
- [Instructions for use](#instructions-for-use)
|
|
14
17
|
- [Demo](#demo)
|
|
18
|
+
- [Roadmap](#roadmap)
|
|
15
19
|
- [License](#license)
|
|
16
20
|
|
|
17
21
|
## Description
|
|
@@ -140,9 +144,13 @@ When you run the application for the first time, it will automatically create th
|
|
|
140
144
|
- **`ivoryos.db`**: Database file that stores application data locally.
|
|
141
145
|
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
## Roadmap
|
|
148
|
+
|
|
149
|
+
- [x] Allow plugin pages ✅
|
|
150
|
+
- [x] pause, resume, abort current and pending workflows ✅
|
|
151
|
+
- [ ] snapshot version control
|
|
152
|
+
- [ ] dropdown input
|
|
153
|
+
- [ ] show line number option
|
|
146
154
|
|
|
147
155
|
|
|
148
156
|
## Authors and Acknowledgement
|
|
@@ -24,10 +24,22 @@ global_config = GlobalConfig()
|
|
|
24
24
|
runner = ScriptRunner()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@socketio.on('
|
|
28
|
-
def
|
|
27
|
+
@socketio.on('abort_pending')
|
|
28
|
+
def handle_abort_pending():
|
|
29
|
+
runner.abort_pending()
|
|
30
|
+
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@socketio.on('abort_current')
|
|
34
|
+
def handle_abort_current():
|
|
29
35
|
runner.stop_execution()
|
|
30
|
-
socketio.emit('log', {'message': "
|
|
36
|
+
socketio.emit('log', {'message': "stopped next task"})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@socketio.on('pause')
|
|
40
|
+
def handle_pause():
|
|
41
|
+
msg = runner.toggle_pause()
|
|
42
|
+
socketio.emit('log', {'message': msg})
|
|
31
43
|
|
|
32
44
|
|
|
33
45
|
@socketio.on('connect')
|
|
@@ -266,9 +278,11 @@ def experiment_run():
|
|
|
266
278
|
config_preview = config[1:6]
|
|
267
279
|
arg_type = config.pop(0) # first entry is types
|
|
268
280
|
try:
|
|
269
|
-
|
|
281
|
+
for key, func_str in exec_string.items():
|
|
282
|
+
exec(func_str)
|
|
283
|
+
line_collection = script.convert_to_lines(exec_string)
|
|
270
284
|
except Exception:
|
|
271
|
-
flash("Please check syntax!!")
|
|
285
|
+
flash(f"Please check {key} syntax!!")
|
|
272
286
|
return redirect(url_for("design.experiment_builder"))
|
|
273
287
|
# runner.globals_dict.update(globals())
|
|
274
288
|
run_name = script.name if script.name else "untitled"
|
|
@@ -310,10 +324,12 @@ def experiment_run():
|
|
|
310
324
|
flash(f"WARNING: Duplicate in config entries.")
|
|
311
325
|
except Exception as e:
|
|
312
326
|
flash(e)
|
|
313
|
-
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
327
|
+
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
328
|
+
dot_py=exec_string, line_collection=line_collection,
|
|
314
329
|
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
315
330
|
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
316
|
-
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
331
|
+
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
332
|
+
history=deck_list)
|
|
317
333
|
|
|
318
334
|
|
|
319
335
|
@design.route("/toggle_script_type/<stype>")
|
|
@@ -547,4 +563,4 @@ def duplicate_action(id: int):
|
|
|
547
563
|
script = utils.get_script_file()
|
|
548
564
|
script.duplicate_action(id)
|
|
549
565
|
utils.post_script_file(script)
|
|
550
|
-
return redirect(back)
|
|
566
|
+
return redirect(back)
|
{ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_run.html
RENAMED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="accordion-body">
|
|
29
29
|
{% if script['script'] or script['prep'] or script['cleanup'] %}
|
|
30
30
|
<div class="row">
|
|
31
|
-
<div class="col-lg-6 col-sm-12">
|
|
31
|
+
<div class="col-lg-6 col-sm-12" id="run-panel">
|
|
32
32
|
<ul class="nav nav-tabs" id="myTabs" role="tablist">
|
|
33
33
|
<li class="nav-item" role="presentation">
|
|
34
34
|
<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>
|
|
@@ -217,13 +217,36 @@
|
|
|
217
217
|
</div>
|
|
218
218
|
</div>
|
|
219
219
|
</div>
|
|
220
|
-
<div class="col-lg-6 col-sm-12
|
|
220
|
+
<div class="col-lg-6 col-sm-12" id="code-panel" style="display: none;">
|
|
221
221
|
<p>
|
|
222
|
-
<div class="p d-flex justify-content-between align-items-center">
|
|
223
222
|
<h5>Progress:</h5>
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
{% for stype, script in line_collection.items() %}
|
|
224
|
+
{% for code in script %}
|
|
225
|
+
<pre style="margin: 0; padding: 0; line-height: 1;"><code class="python" id="{{ stype }}-{{ loop.index0 }}" >{{code}}</code></pre>
|
|
226
|
+
{% endfor %}
|
|
227
|
+
{% endfor %}
|
|
226
228
|
</p>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="col-lg-6 col-sm-12 logging-panel">
|
|
231
|
+
<p>
|
|
232
|
+
<div class="p d-flex justify-content-between align-items-center">
|
|
233
|
+
<h5>Progress:</h5>
|
|
234
|
+
<div class="d-flex gap-2 ms-auto">
|
|
235
|
+
<button id="pause-resume" class="btn btn-info text-white" data-bs-toggle="tooltip" title="Pause execution">
|
|
236
|
+
<i class="bi bi-pause-circle"></i> <!-- Icon for Pause -->
|
|
237
|
+
</button>
|
|
238
|
+
<button id="abort-current" class="btn btn-danger text-white" data-bs-toggle="tooltip" title="Stop execution after current step">
|
|
239
|
+
<i class="bi bi-stop-circle"></i> <!-- Icon for Stop After This Step -->
|
|
240
|
+
</button>
|
|
241
|
+
<button id="abort-pending" class="btn btn-warning text-white" data-bs-toggle="tooltip" title="Stop execution after current iteration">
|
|
242
|
+
<i class="bi bi-hourglass-split"></i> <!-- Icon for Stop After This Iteration -->
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="text-muted mt-2">
|
|
247
|
+
<small><strong>Note:</strong> The current step cannot be paused or stopped until it completes. </small>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
227
250
|
<div class="progress" role="progressbar" aria-label="Animated striped example" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100">
|
|
228
251
|
<div id="progress-bar-inner" class="progress-bar progress-bar-striped progress-bar-animated"></div>
|
|
229
252
|
</div>
|
|
@@ -295,7 +318,9 @@
|
|
|
295
318
|
</h2>
|
|
296
319
|
<div id="python" class="accordion-collapse collapse">
|
|
297
320
|
<div class="accordion-body">
|
|
298
|
-
|
|
321
|
+
{% for stype, script in dot_py.items() %}
|
|
322
|
+
<pre><code class="python" >{{script}}</code></pre>
|
|
323
|
+
{% endfor %}
|
|
299
324
|
<a href="{{ url_for('design.download', filetype='python') }}">Download <i class="bi bi-download"></i></a>
|
|
300
325
|
</div>
|
|
301
326
|
</div>
|
|
@@ -322,4 +347,22 @@
|
|
|
322
347
|
}
|
|
323
348
|
});
|
|
324
349
|
</script>
|
|
350
|
+
|
|
351
|
+
<script>
|
|
352
|
+
var socket = io.connect(window.location.origin);
|
|
353
|
+
|
|
354
|
+
socket.on('execution', function(data) {
|
|
355
|
+
// Remove highlighting from all lines
|
|
356
|
+
document.querySelectorAll('pre code').forEach(el => el.style.backgroundColor = '');
|
|
357
|
+
|
|
358
|
+
// Get the currently executing line and highlight it
|
|
359
|
+
let executingLine = document.getElementById(data.section);
|
|
360
|
+
if (executingLine) {
|
|
361
|
+
executingLine.style.backgroundColor = '#cce5ff'; // Highlight
|
|
362
|
+
executingLine.style.transition = 'background-color 0.3s ease-in-out';
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
</script>
|
|
367
|
+
|
|
325
368
|
{% endblock %}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
2
|
+
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
|
3
|
+
socket.on('connect', function() {
|
|
4
|
+
console.log('Connected');
|
|
5
|
+
});
|
|
6
|
+
socket.on('progress', function(data) {
|
|
7
|
+
var progress = data.progress;
|
|
8
|
+
console.log(progress);
|
|
9
|
+
// Update the progress bar's width and appearance
|
|
10
|
+
var progressBar = document.getElementById('progress-bar-inner');
|
|
11
|
+
progressBar.style.width = progress + '%';
|
|
12
|
+
progressBar.setAttribute('aria-valuenow', progress);
|
|
13
|
+
if (progress === 1) {
|
|
14
|
+
document.getElementById("run-panel").style.display = "none";
|
|
15
|
+
document.getElementById("code-panel").style.display = "block";
|
|
16
|
+
|
|
17
|
+
// Optional: Scroll to the code panel
|
|
18
|
+
document.getElementById("code-panel").scrollIntoView({ behavior: "smooth" });
|
|
19
|
+
}
|
|
20
|
+
if (progress === 100) {
|
|
21
|
+
// Remove animation and set green color when 100% is reached
|
|
22
|
+
progressBar.classList.remove('progress-bar-animated');
|
|
23
|
+
progressBar.classList.add('bg-success'); // Bootstrap class for green color
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
document.getElementById("code-panel").style.display = "none";
|
|
26
|
+
document.getElementById("run-panel").style.display = "block";
|
|
27
|
+
}, 1000); // Small delay to let users see the completion
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
socket.on('log', function(data) {
|
|
31
|
+
var logMessage = data.message;
|
|
32
|
+
console.log(logMessage);
|
|
33
|
+
$('#logging-panel').append(logMessage + "<br>");
|
|
34
|
+
$('#logging-panel').scrollTop($('#logging-panel')[0].scrollHeight);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
document.getElementById('abort-pending').addEventListener('click', function() {
|
|
38
|
+
var confirmation = confirm("Are you sure you want to stop after this iteration?");
|
|
39
|
+
if (confirmation) {
|
|
40
|
+
socket.emit('abort_pending');
|
|
41
|
+
console.log('Abort action sent to server.');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
document.getElementById('abort-current').addEventListener('click', function() {
|
|
45
|
+
var confirmation = confirm("Are you sure you want to stop after this step?");
|
|
46
|
+
if (confirmation) {
|
|
47
|
+
socket.emit('abort_current');
|
|
48
|
+
console.log('Stop action sent to server.');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
document.getElementById('pause-resume').addEventListener('click', function() {
|
|
53
|
+
|
|
54
|
+
socket.emit('pause');
|
|
55
|
+
console.log('Pause/Resume is toggled.');
|
|
56
|
+
var button = this;
|
|
57
|
+
var icon = button.querySelector("i");
|
|
58
|
+
|
|
59
|
+
// Toggle between Pause and Resume
|
|
60
|
+
if (icon.classList.contains("bi-pause-circle")) {
|
|
61
|
+
icon.classList.remove("bi-pause-circle");
|
|
62
|
+
icon.classList.add("bi-play-circle");
|
|
63
|
+
button.innerHTML = '<i class="bi bi-play-circle"></i>';
|
|
64
|
+
button.setAttribute("title", "Resume execution");
|
|
65
|
+
} else {
|
|
66
|
+
icon.classList.remove("bi-play-circle");
|
|
67
|
+
icon.classList.add("bi-pause-circle");
|
|
68
|
+
button.innerHTML = '<i class="bi bi-pause-circle"></i>';
|
|
69
|
+
button.setAttribute("title", "Pause execution");
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -390,6 +390,23 @@ class Script(db.Model):
|
|
|
390
390
|
string += "\t"
|
|
391
391
|
return string
|
|
392
392
|
|
|
393
|
+
def convert_to_lines(self, exec_str_collection: dict):
|
|
394
|
+
"""
|
|
395
|
+
Parse a dictionary of script functions and extract function body lines.
|
|
396
|
+
|
|
397
|
+
:param exec_str_collection: Dictionary containing script types and corresponding function strings.
|
|
398
|
+
:return: A dict containing script types as keys and lists of function body lines as values.
|
|
399
|
+
"""
|
|
400
|
+
line_collection = {}
|
|
401
|
+
for stype, func_str in exec_str_collection.items():
|
|
402
|
+
module = ast.parse(func_str)
|
|
403
|
+
func_def = next(node for node in module.body if isinstance(node, ast.FunctionDef))
|
|
404
|
+
|
|
405
|
+
# Extract function body as source lines
|
|
406
|
+
line_collection[stype] = [ast.unparse(node) for node in func_def.body if not isinstance(node, ast.Return)]
|
|
407
|
+
# print(line_collection[stype])
|
|
408
|
+
return line_collection
|
|
409
|
+
|
|
393
410
|
def compile(self, script_path=None):
|
|
394
411
|
"""
|
|
395
412
|
Compile the current script to a Python file.
|
|
@@ -398,35 +415,17 @@ class Script(db.Model):
|
|
|
398
415
|
self.sort_actions()
|
|
399
416
|
run_name = self.name if self.name else "untitled"
|
|
400
417
|
run_name = self.validate_function_name(run_name)
|
|
401
|
-
|
|
418
|
+
exec_str_collection = {}
|
|
402
419
|
|
|
403
420
|
for i in self.stypes:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
if self.script_dict[i]:
|
|
422
|
+
func_str = self._generate_function_header(run_name, i) + self._generate_function_body(i)
|
|
423
|
+
exec_str_collection[i] = func_str
|
|
407
424
|
if script_path:
|
|
408
|
-
self._write_to_file(script_path, run_name,
|
|
409
|
-
|
|
410
|
-
return exec_string
|
|
411
|
-
|
|
412
|
-
def compile_steps(self, script_path=None):
|
|
413
|
-
"""
|
|
414
|
-
Compile the current script to steps.
|
|
415
|
-
:return: {"prep":[], "script":[], "cleanup":[],}.
|
|
416
|
-
"""
|
|
417
|
-
self.sort_actions()
|
|
418
|
-
run_name = self.name if self.name else "untitled"
|
|
419
|
-
run_name = self.validate_function_name(run_name)
|
|
420
|
-
exec_string = ''
|
|
421
|
-
steps = {}
|
|
422
|
-
for i in self.stypes:
|
|
423
|
-
# exec_string += self._generate_function_header(run_name, i)
|
|
424
|
-
exec_string += self._generate_function_body(i)
|
|
425
|
+
self._write_to_file(script_path, run_name, exec_str_collection)
|
|
425
426
|
|
|
426
|
-
|
|
427
|
-
self._write_to_file(script_path, run_name, exec_string)
|
|
427
|
+
return exec_str_collection
|
|
428
428
|
|
|
429
|
-
return exec_string
|
|
430
429
|
|
|
431
430
|
|
|
432
431
|
@staticmethod
|
|
@@ -447,13 +446,13 @@ class Script(db.Model):
|
|
|
447
446
|
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
448
447
|
config_type.items()]
|
|
449
448
|
|
|
450
|
-
function_header = f"
|
|
449
|
+
function_header = f"def {run_name}_{stype}("
|
|
451
450
|
|
|
452
451
|
if stype == "script":
|
|
453
452
|
function_header += ", ".join(configure)
|
|
454
453
|
|
|
455
454
|
function_header += "):"
|
|
456
|
-
function_header += self.indent(1) + f"global {run_name}_{stype}"
|
|
455
|
+
# function_header += self.indent(1) + f"global {run_name}_{stype}"
|
|
457
456
|
return function_header
|
|
458
457
|
|
|
459
458
|
def _generate_function_body(self, stype):
|
|
@@ -471,21 +470,6 @@ class Script(db.Model):
|
|
|
471
470
|
body += self.indent(indent_unit) + return_str
|
|
472
471
|
return body
|
|
473
472
|
|
|
474
|
-
# def _generate_function_body(self, stype):
|
|
475
|
-
# """
|
|
476
|
-
# Generate the function body for each type in stypes.
|
|
477
|
-
# """
|
|
478
|
-
# steps = []
|
|
479
|
-
# indent_unit = 1
|
|
480
|
-
#
|
|
481
|
-
# for index, action in enumerate(self.script_dict[stype]):
|
|
482
|
-
# text, indent_unit = self._process_action(indent_unit, action, index, stype)
|
|
483
|
-
# body += text
|
|
484
|
-
# return_str, return_list = self.config_return()
|
|
485
|
-
# if return_list and stype == "script":
|
|
486
|
-
# body += self.indent(indent_unit) + return_str
|
|
487
|
-
# return body
|
|
488
|
-
|
|
489
473
|
def _process_action(self, indent_unit, action, index, stype):
|
|
490
474
|
"""
|
|
491
475
|
Process each action within the script dictionary.
|
|
@@ -631,7 +615,8 @@ class Script(db.Model):
|
|
|
631
615
|
else:
|
|
632
616
|
s.write("deck = None")
|
|
633
617
|
s.write("\nimport time")
|
|
634
|
-
|
|
618
|
+
for i in exec_string.values():
|
|
619
|
+
s.write(f"\n\n\n{i}")
|
|
635
620
|
|
|
636
621
|
|
|
637
622
|
if __name__ == "__main__":
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ast
|
|
1
2
|
import os
|
|
2
3
|
import csv
|
|
3
4
|
import threading
|
|
@@ -17,25 +18,42 @@ class ScriptRunner:
|
|
|
17
18
|
if globals_dict is None:
|
|
18
19
|
globals_dict = globals()
|
|
19
20
|
self.globals_dict = globals_dict
|
|
20
|
-
|
|
21
|
-
self.
|
|
21
|
+
self.pause_event = threading.Event() # A threading event to manage pause/resume
|
|
22
|
+
self.pause_event.set()
|
|
23
|
+
self.stop_pending_event = threading.Event()
|
|
24
|
+
self.stop_current_event = threading.Event()
|
|
22
25
|
self.is_running = False
|
|
23
26
|
self.lock = threading.Lock()
|
|
24
27
|
|
|
28
|
+
def toggle_pause(self):
|
|
29
|
+
"""Toggles between pausing and resuming the script"""
|
|
30
|
+
if self.pause_event.is_set():
|
|
31
|
+
self.pause_event.clear() # Pause the script
|
|
32
|
+
return "Paused"
|
|
33
|
+
else:
|
|
34
|
+
self.pause_event.set() # Resume the script
|
|
35
|
+
return "Resumed"
|
|
36
|
+
|
|
25
37
|
def reset_stop_event(self):
|
|
26
|
-
|
|
38
|
+
"""Resets the stop event"""
|
|
39
|
+
self.stop_pending_event.clear()
|
|
40
|
+
self.stop_current_event.clear()
|
|
27
41
|
|
|
28
|
-
def
|
|
29
|
-
|
|
42
|
+
def abort_pending(self):
|
|
43
|
+
"""Abort the pending iteration after the current is finished"""
|
|
44
|
+
self.stop_pending_event.set()
|
|
30
45
|
# print("Stop pending tasks")
|
|
31
46
|
|
|
47
|
+
def stop_execution(self):
|
|
48
|
+
"""Force stop everything, including ongoing tasks."""
|
|
49
|
+
self.stop_current_event.set()
|
|
50
|
+
self.abort_pending()
|
|
51
|
+
|
|
32
52
|
def run_script(self, script, repeat_count=1, run_name=None, logger=None, socketio=None, config=None, bo_args=None,
|
|
33
53
|
output_path=""):
|
|
34
54
|
global deck
|
|
35
55
|
if deck is None:
|
|
36
56
|
deck = global_config.deck
|
|
37
|
-
exec_string = script.compile()
|
|
38
|
-
exec(exec_string)
|
|
39
57
|
time.sleep(1)
|
|
40
58
|
with self.lock:
|
|
41
59
|
if self.is_running:
|
|
@@ -50,24 +68,63 @@ class ScriptRunner:
|
|
|
50
68
|
thread.start()
|
|
51
69
|
return thread
|
|
52
70
|
|
|
53
|
-
def
|
|
71
|
+
def execute_function_line_by_line(self, lines:list, section_name, logger, socketio, **kwargs):
|
|
72
|
+
"""
|
|
73
|
+
Executes a function defined in a string line by line.
|
|
74
|
+
|
|
75
|
+
:param func_str: The function as a string
|
|
76
|
+
:param kwargs: Arguments to pass to the function
|
|
77
|
+
:return: The final result of the function execution
|
|
78
|
+
"""
|
|
79
|
+
global deck
|
|
80
|
+
if deck is None:
|
|
81
|
+
deck = global_config.deck
|
|
82
|
+
# func_str = script.compile()
|
|
83
|
+
# Parse function body from string
|
|
84
|
+
|
|
85
|
+
# Prepare execution environment
|
|
86
|
+
exec_globals = {"deck": deck} # Add required global objects
|
|
87
|
+
exec_locals = {} # Local execution scope
|
|
88
|
+
|
|
89
|
+
# Define function arguments manually in exec_locals
|
|
90
|
+
exec_locals.update(kwargs)
|
|
91
|
+
|
|
92
|
+
# Execute each line dynamically
|
|
93
|
+
for index, line in enumerate(lines):
|
|
94
|
+
if self.stop_current_event.is_set():
|
|
95
|
+
logger.info(f'Stopping execution during {section_name}')
|
|
96
|
+
break
|
|
97
|
+
logger.info(f"Executing: {line}") # Debugging output
|
|
98
|
+
socketio.emit('execution', {'section': f"{section_name}-{index}"})
|
|
99
|
+
# self._emit_progress(socketio, 100)
|
|
100
|
+
exec(line, exec_globals, exec_locals)
|
|
101
|
+
self.pause_event.wait()
|
|
102
|
+
|
|
103
|
+
return exec_locals # Return the 'results' variable
|
|
104
|
+
|
|
105
|
+
def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
|
|
54
106
|
output_path):
|
|
55
107
|
time.sleep(1)
|
|
108
|
+
func_str = script.compile()
|
|
109
|
+
func_str_lines = script.convert_to_lines(func_str)
|
|
56
110
|
self._emit_progress(socketio, 1)
|
|
57
111
|
try:
|
|
58
112
|
# Run "prep" section once
|
|
59
113
|
script_dict = script.script_dict
|
|
60
|
-
self._run_actions(script_dict.get("prep", []), section_name="prep",
|
|
114
|
+
self._run_actions(script_dict.get("prep", []), func_str_lines.get("prep", []), section_name="prep", logger=logger, socketio=socketio)
|
|
61
115
|
output_list = []
|
|
62
116
|
_, arg_type = script.config("script")
|
|
63
117
|
_, return_list = script.config_return()
|
|
64
118
|
# Run "script" section multiple times
|
|
65
119
|
if repeat_count:
|
|
66
|
-
self._run_repeat_section(repeat_count, arg_type, bo_args, output_list,
|
|
120
|
+
self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, func_str_lines.get("script", []),
|
|
121
|
+
run_name, return_list, logger, socketio)
|
|
67
122
|
elif config:
|
|
68
|
-
self._run_config_section(config, arg_type, output_list,
|
|
123
|
+
self._run_config_section(config, arg_type, output_list, func_str_lines.get("script", []), run_name, logger,
|
|
124
|
+
socketio)
|
|
69
125
|
# Run "cleanup" section once
|
|
70
|
-
self._run_actions(script_dict.get("cleanup", []),
|
|
126
|
+
self._run_actions(script_dict.get("cleanup", []), func_str.get("cleanup", []), section_name="cleanup",
|
|
127
|
+
logger=logger, socketio=socketio)
|
|
71
128
|
# Save results if necessary
|
|
72
129
|
if output_list:
|
|
73
130
|
self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
|
|
@@ -78,21 +135,14 @@ class ScriptRunner:
|
|
|
78
135
|
self.is_running = False # Reset the running flag when done
|
|
79
136
|
self._emit_progress(socketio, 100)
|
|
80
137
|
|
|
81
|
-
def _run_actions(self, actions, section_name="",
|
|
138
|
+
def _run_actions(self, actions, func_str, section_name="", logger=None, socketio=None):
|
|
82
139
|
logger.info(f'Executing {section_name} steps') if actions else logger.info(f'No {section_name} steps')
|
|
83
|
-
if self.
|
|
140
|
+
if self.stop_pending_event.is_set():
|
|
84
141
|
logger.info(f"Stopping execution during {section_name} section.")
|
|
85
142
|
return
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# break
|
|
90
|
-
# logger.info(f'Executing {action.get("action", "")} action')
|
|
91
|
-
fname = f"{run_name}_{section_name}"
|
|
92
|
-
function = self.globals_dict[fname]
|
|
93
|
-
function()
|
|
94
|
-
|
|
95
|
-
def _run_config_section(self, config, arg_type, output_list, return_list, run_name, logger, socketio):
|
|
143
|
+
self.execute_function_line_by_line(func_str, section_name, logger, socketio)
|
|
144
|
+
|
|
145
|
+
def _run_config_section(self, config, arg_type, output_list, func_str, run_name, logger, socketio):
|
|
96
146
|
compiled = True
|
|
97
147
|
for i in config:
|
|
98
148
|
try:
|
|
@@ -104,25 +154,26 @@ class ScriptRunner:
|
|
|
104
154
|
if compiled:
|
|
105
155
|
for i, kwargs in enumerate(config):
|
|
106
156
|
kwargs = dict(kwargs)
|
|
107
|
-
if self.
|
|
157
|
+
if self.stop_pending_event.is_set():
|
|
108
158
|
logger.info(f'Stopping execution during {run_name}: {i + 1}/{len(config)}')
|
|
109
159
|
break
|
|
110
160
|
logger.info(f'Executing {i + 1} of {len(config)} with kwargs = {kwargs}')
|
|
111
161
|
progress = (i + 1) * 100 / len(config)
|
|
112
162
|
self._emit_progress(socketio, progress)
|
|
113
|
-
fname = f"{run_name}_script"
|
|
114
|
-
function = self.globals_dict[fname]
|
|
115
|
-
output =
|
|
163
|
+
# fname = f"{run_name}_script"
|
|
164
|
+
# function = self.globals_dict[fname]
|
|
165
|
+
output = self.execute_function_line_by_line(func_str, "script", logger, socketio, **kwargs)
|
|
116
166
|
if output:
|
|
117
|
-
kwargs.update(output)
|
|
118
|
-
output_list.append(
|
|
167
|
+
# kwargs.update(output)
|
|
168
|
+
output_list.append(output)
|
|
119
169
|
|
|
120
|
-
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list,
|
|
170
|
+
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, func_str, run_name, return_list,
|
|
171
|
+
logger, socketio):
|
|
121
172
|
if bo_args:
|
|
122
173
|
logger.info('Initializing optimizer...')
|
|
123
174
|
ax_client = utils.ax_initiation(bo_args, arg_types)
|
|
124
175
|
for i in range(int(repeat_count)):
|
|
125
|
-
if self.
|
|
176
|
+
if self.stop_pending_event.is_set():
|
|
126
177
|
logger.info(f'Stopping execution during {run_name}: {i + 1}/{int(repeat_count)}')
|
|
127
178
|
break
|
|
128
179
|
logger.info(f'Executing {run_name} experiment: {i + 1}/{int(repeat_count)}')
|
|
@@ -132,20 +183,20 @@ class ScriptRunner:
|
|
|
132
183
|
try:
|
|
133
184
|
parameters, trial_index = ax_client.get_next_trial()
|
|
134
185
|
logger.info(f'Output value: {parameters}')
|
|
135
|
-
fname = f"{run_name}_script"
|
|
136
|
-
function = self.globals_dict[fname]
|
|
137
|
-
output =
|
|
138
|
-
|
|
139
|
-
_output = output.
|
|
186
|
+
# fname = f"{run_name}_script"
|
|
187
|
+
# function = self.globals_dict[fname]
|
|
188
|
+
output = self.execute_function_line_by_line(func_str, "script", logger, socketio, **parameters)
|
|
189
|
+
|
|
190
|
+
_output = {key: value for key, value in output.items() if key in return_list}
|
|
140
191
|
ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
|
|
141
192
|
output.update(parameters)
|
|
142
193
|
except Exception as e:
|
|
143
194
|
logger.info(f'Optimization error: {e}')
|
|
144
195
|
break
|
|
145
196
|
else:
|
|
146
|
-
fname = f"{run_name}_script"
|
|
147
|
-
function = self.globals_dict[fname]
|
|
148
|
-
output =
|
|
197
|
+
# fname = f"{run_name}_script"
|
|
198
|
+
# function = self.globals_dict[fname]
|
|
199
|
+
output = self.execute_function_line_by_line(func_str, "script", logger, socketio)
|
|
149
200
|
|
|
150
201
|
if output:
|
|
151
202
|
output_list.append(output)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.20"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
6
|
Author: Ivory Zhang
|
|
@@ -13,6 +13,9 @@ License-File: LICENSE
|
|
|
13
13
|
[](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
|
|
14
14
|
[](https://pypi.org/project/ivoryos/)
|
|
15
15
|

|
|
16
|
+
[](https://youtu.be/dFfJv9I2-1g)
|
|
17
|
+
[](https://www.researchsquare.com/article/rs-5307798/v1)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|

|
|
18
21
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -24,6 +27,7 @@ License-File: LICENSE
|
|
|
24
27
|
- [Installation](#installation)
|
|
25
28
|
- [Instructions for use](#instructions-for-use)
|
|
26
29
|
- [Demo](#demo)
|
|
30
|
+
- [Roadmap](#roadmap)
|
|
27
31
|
- [License](#license)
|
|
28
32
|
|
|
29
33
|
## Description
|
|
@@ -152,9 +156,13 @@ When you run the application for the first time, it will automatically create th
|
|
|
152
156
|
- **`ivoryos.db`**: Database file that stores application data locally.
|
|
153
157
|
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
## Roadmap
|
|
160
|
+
|
|
161
|
+
- [x] Allow plugin pages ✅
|
|
162
|
+
- [x] pause, resume, abort current and pending workflows ✅
|
|
163
|
+
- [ ] snapshot version control
|
|
164
|
+
- [ ] dropdown input
|
|
165
|
+
- [ ] show line number option
|
|
158
166
|
|
|
159
167
|
|
|
160
168
|
## Authors and Acknowledgement
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
document.addEventListener("DOMContentLoaded", function() {
|
|
2
|
-
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
|
3
|
-
socket.on('connect', function() {
|
|
4
|
-
console.log('Connected');
|
|
5
|
-
});
|
|
6
|
-
socket.on('progress', function(data) {
|
|
7
|
-
var progress = data.progress;
|
|
8
|
-
console.log(progress);
|
|
9
|
-
// Update the progress bar's width and appearance
|
|
10
|
-
var progressBar = document.getElementById('progress-bar-inner');
|
|
11
|
-
progressBar.style.width = progress + '%';
|
|
12
|
-
progressBar.setAttribute('aria-valuenow', progress);
|
|
13
|
-
|
|
14
|
-
if (progress === 100) {
|
|
15
|
-
// Remove animation and set green color when 100% is reached
|
|
16
|
-
progressBar.classList.remove('progress-bar-animated');
|
|
17
|
-
progressBar.classList.add('bg-success'); // Bootstrap class for green color
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
socket.on('log', function(data) {
|
|
21
|
-
var logMessage = data.message;
|
|
22
|
-
console.log(logMessage);
|
|
23
|
-
$('#logging-panel').append(logMessage + "<br>");
|
|
24
|
-
$('#logging-panel').scrollTop($('#logging-panel')[0].scrollHeight);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
document.getElementById('abort').addEventListener('click', function() {
|
|
28
|
-
var confirmation = confirm("Are you sure you want to abort pending actions?");
|
|
29
|
-
if (confirmation) {
|
|
30
|
-
socket.emit('abort_action');
|
|
31
|
-
console.log('Abort action sent to server.');
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.18"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_home.html
RENAMED
|
File without changes
|
{ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_new.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-0.1.18 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_builder.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|