ivoryos 0.1.20__py3-none-any.whl → 0.1.21__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.
- ivoryos/__init__.py +8 -11
- ivoryos/routes/database/database.py +2 -0
- ivoryos/routes/database/templates/database/experiment_database.html +3 -1
- ivoryos/routes/design/design.py +58 -16
- ivoryos/routes/design/templates/design/experiment_builder.html +84 -94
- ivoryos/routes/design/templates/design/experiment_run.html +30 -21
- ivoryos/static/js/socket_handler.js +59 -16
- ivoryos/static/js/sortable_design.js +102 -33
- ivoryos/static/style.css +9 -0
- ivoryos/templates/base.html +3 -3
- ivoryos/utils/db_models.py +23 -10
- ivoryos/utils/form.py +50 -2
- ivoryos/utils/global_config.py +10 -0
- ivoryos/utils/script_runner.py +65 -42
- ivoryos/utils/utils.py +1 -1
- ivoryos/version.py +1 -1
- {ivoryos-0.1.20.dist-info → ivoryos-0.1.21.dist-info}/METADATA +1 -1
- {ivoryos-0.1.20.dist-info → ivoryos-0.1.21.dist-info}/RECORD +21 -21
- {ivoryos-0.1.20.dist-info → ivoryos-0.1.21.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.20.dist-info → ivoryos-0.1.21.dist-info}/WHEEL +0 -0
- {ivoryos-0.1.20.dist-info → ivoryos-0.1.21.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,57 @@ document.addEventListener("DOMContentLoaded", function() {
|
|
|
27
27
|
}, 1000); // Small delay to let users see the completion
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
socket.on('error', function(errorData) {
|
|
32
|
+
console.error("Error received:", errorData);
|
|
33
|
+
var progressBar = document.getElementById('progress-bar-inner');
|
|
34
|
+
|
|
35
|
+
progressBar.classList.remove('bg-success');
|
|
36
|
+
progressBar.classList.add('bg-danger'); // Red color for error
|
|
37
|
+
// Show error modal
|
|
38
|
+
var errorModal = new bootstrap.Modal(document.getElementById('error-modal'));
|
|
39
|
+
document.getElementById('error-message').innerText = "An error occurred: " + errorData.message;
|
|
40
|
+
errorModal.show();
|
|
41
|
+
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Handle Pause/Resume Button
|
|
45
|
+
document.getElementById('pause-resume').addEventListener('click', function() {
|
|
46
|
+
socket.emit('pause');
|
|
47
|
+
console.log('Pause/Resume is toggled.');
|
|
48
|
+
var button = this;
|
|
49
|
+
var icon = button.querySelector("i");
|
|
50
|
+
|
|
51
|
+
// Toggle Pause and Resume
|
|
52
|
+
if (icon.classList.contains("bi-pause-circle")) {
|
|
53
|
+
icon.classList.remove("bi-pause-circle");
|
|
54
|
+
icon.classList.add("bi-play-circle");
|
|
55
|
+
button.innerHTML = '<i class="bi bi-play-circle"></i>';
|
|
56
|
+
button.setAttribute("title", "Resume execution");
|
|
57
|
+
} else {
|
|
58
|
+
icon.classList.remove("bi-play-circle");
|
|
59
|
+
icon.classList.add("bi-pause-circle");
|
|
60
|
+
button.innerHTML = '<i class="bi bi-pause-circle"></i>';
|
|
61
|
+
button.setAttribute("title", "Pause execution");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle Modal Buttons
|
|
66
|
+
document.getElementById('continue-btn').addEventListener('click', function() {
|
|
67
|
+
socket.emit('pause'); // Resume execution
|
|
68
|
+
console.log("Execution resumed.");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
document.getElementById('stop-btn').addEventListener('click', function() {
|
|
72
|
+
socket.emit('pause'); // Resume execution
|
|
73
|
+
socket.emit('abort_current'); // Stop execution
|
|
74
|
+
console.log("Execution stopped.");
|
|
75
|
+
|
|
76
|
+
// Reset UI back to initial state
|
|
77
|
+
document.getElementById("code-panel").style.display = "none";
|
|
78
|
+
document.getElementById("run-panel").style.display = "block";
|
|
79
|
+
});
|
|
80
|
+
|
|
30
81
|
socket.on('log', function(data) {
|
|
31
82
|
var logMessage = data.message;
|
|
32
83
|
console.log(logMessage);
|
|
@@ -49,24 +100,16 @@ document.addEventListener("DOMContentLoaded", function() {
|
|
|
49
100
|
}
|
|
50
101
|
});
|
|
51
102
|
|
|
52
|
-
|
|
103
|
+
socket.on('execution', function(data) {
|
|
104
|
+
// Remove highlighting from all lines
|
|
105
|
+
document.querySelectorAll('pre code').forEach(el => el.style.backgroundColor = '');
|
|
53
106
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
107
|
+
// Get the currently executing line and highlight it
|
|
108
|
+
let executingLine = document.getElementById(data.section);
|
|
109
|
+
if (executingLine) {
|
|
110
|
+
executingLine.style.backgroundColor = '#cce5ff'; // Highlight
|
|
111
|
+
executingLine.style.transition = 'background-color 0.3s ease-in-out';
|
|
58
112
|
|
|
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
113
|
}
|
|
71
114
|
});
|
|
72
115
|
});
|
|
@@ -1,36 +1,105 @@
|
|
|
1
|
-
$(document).ready(function(){
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
$(document).ready(function () {
|
|
2
|
+
let dropTargetId = ""; // Store the ID of the drop target
|
|
3
|
+
|
|
4
|
+
$("#list ul").sortable({
|
|
5
|
+
cancel: ".unsortable",
|
|
6
|
+
opacity: 0.8,
|
|
7
|
+
cursor: "move",
|
|
8
|
+
placeholder: "drop-placeholder",
|
|
9
|
+
update: function () {
|
|
10
|
+
var item_order = [];
|
|
11
|
+
$("ul.reorder li").each(function () {
|
|
12
|
+
item_order.push($(this).attr("id"));
|
|
13
|
+
});
|
|
14
|
+
var order_string = "order=" + item_order.join(",");
|
|
15
|
+
|
|
16
|
+
$.ajax({
|
|
17
|
+
method: "POST",
|
|
18
|
+
url: updateListUrl,
|
|
19
|
+
data: order_string,
|
|
20
|
+
cache: false,
|
|
21
|
+
success: function (data) {
|
|
22
|
+
$("#response").html(data);
|
|
23
|
+
$("#response").slideDown("slow");
|
|
24
|
+
slideout();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 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
|
|
34
|
+
event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
|
|
35
|
+
event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
|
|
36
|
+
|
|
37
|
+
$(this).addClass("dragging");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
$("#list ul, .canvas").on("dragover", function (event) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
let $target = $(event.target).closest("li");
|
|
44
|
+
|
|
45
|
+
// If we're over a valid <li> element in the list
|
|
46
|
+
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
|
|
51
|
+
} 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
|
|
54
|
+
} else {
|
|
55
|
+
dropTargetId = ""; // Append placeholder to canvas
|
|
56
|
+
}
|
|
57
|
+
});
|
|
7
58
|
|
|
8
|
-
$("#
|
|
9
|
-
|
|
10
|
-
$(function() {
|
|
11
|
-
$("#list ul").sortable({
|
|
12
|
-
cancel: ".unsortable",
|
|
13
|
-
opacity: 0.8,
|
|
14
|
-
cursor: 'move',
|
|
15
|
-
update: function() {
|
|
16
|
-
var item_order = [];
|
|
17
|
-
$('ul.reorder li').each(function() {
|
|
18
|
-
item_order.push($(this).attr("id"));
|
|
19
|
-
});
|
|
20
|
-
var order_string = 'order=' + item_order.join(',');
|
|
21
|
-
|
|
22
|
-
$.ajax({
|
|
23
|
-
method: "POST",
|
|
24
|
-
url: updateListUrl, // updateListUrl should be set dynamically in your HTML template
|
|
25
|
-
data: order_string,
|
|
26
|
-
cache: false,
|
|
27
|
-
success: function(data){
|
|
28
|
-
$("#response").html(data);
|
|
29
|
-
$("#response").slideDown('slow');
|
|
30
|
-
slideout();
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
59
|
+
$("#list ul, .canvas").on("dragleave", function () {
|
|
60
|
+
$(".drop-placeholder").remove(); // Remove placeholder on leave
|
|
35
61
|
});
|
|
62
|
+
|
|
63
|
+
$("#list ul, .canvas").on("drop", function (event) {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
|
|
66
|
+
var actionName = event.originalEvent.dataTransfer.getData("action");
|
|
67
|
+
var actionId = event.originalEvent.dataTransfer.getData("id");
|
|
68
|
+
var formHtml = event.originalEvent.dataTransfer.getData("form"); // Retrieve form HTML
|
|
69
|
+
let listLength = $("ul.reorder li").length;
|
|
70
|
+
dropTargetId = dropTargetId || listLength + 1; // Assign a "last" ID or unique identifier
|
|
71
|
+
$(".drop-placeholder").remove();
|
|
72
|
+
// Trigger the modal with the appropriate action
|
|
73
|
+
triggerModal(formHtml, actionName, actionId, dropTargetId);
|
|
74
|
+
|
|
75
|
+
});
|
|
76
|
+
|
|
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
|
|
81
|
+
|
|
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);
|
|
88
|
+
|
|
89
|
+
// Insert before the submit button
|
|
90
|
+
$form.find("button[type='submit']").before($hiddenInput);
|
|
91
|
+
|
|
92
|
+
$("#modalFormFields").empty().append($form.children());
|
|
93
|
+
$("#dropModal").modal("show"); // Show modal
|
|
94
|
+
|
|
95
|
+
// Store and display drop target ID in the modal
|
|
96
|
+
$("#modalDropTarget").text(dropTargetId || "N/A");
|
|
97
|
+
|
|
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
|
+
}
|
|
36
105
|
});
|
ivoryos/static/style.css
CHANGED
|
@@ -199,4 +199,13 @@ hr.vertical {
|
|
|
199
199
|
color: white;
|
|
200
200
|
font-size: 24px;
|
|
201
201
|
padding-top: 20%;
|
|
202
|
+
}
|
|
203
|
+
.drop-placeholder {
|
|
204
|
+
height: 2px !important; /* Keep it very thin */
|
|
205
|
+
min-height: 2px !important;
|
|
206
|
+
margin: 0 !important;
|
|
207
|
+
padding: 0 !important;
|
|
208
|
+
background: rgba(0, 0, 0, 0.2); /* Slight visibility */
|
|
209
|
+
border-radius: 2px;
|
|
210
|
+
list-style: none; /* Remove any default list styling */
|
|
202
211
|
}
|
ivoryos/templates/base.html
CHANGED
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
<li class="nav-item">
|
|
55
55
|
<a class="nav-link" href="{{ url_for('control.controllers_home') }}">Temp Devices</a></li>
|
|
56
56
|
</li>
|
|
57
|
-
<li class="nav-item"
|
|
58
|
-
<a class="nav-link" href="{{ url_for('main.help_info') }}">About</a
|
|
59
|
-
</li
|
|
57
|
+
{# <li class="nav-item">#}
|
|
58
|
+
{# <a class="nav-link" href="{{ url_for('main.help_info') }}">About</a>#}
|
|
59
|
+
{# </li>#}
|
|
60
60
|
{% if plugins %}
|
|
61
61
|
{% for plugin in plugins %}
|
|
62
62
|
<li class="nav-item">
|
ivoryos/utils/db_models.py
CHANGED
|
@@ -196,28 +196,37 @@ class Script(db.Model):
|
|
|
196
196
|
for i in self.stypes:
|
|
197
197
|
self._sort(i)
|
|
198
198
|
|
|
199
|
-
def add_action(self, action: dict):
|
|
199
|
+
def add_action(self, action: dict, insert_position=None):
|
|
200
200
|
current_len = len(self.currently_editing_script)
|
|
201
201
|
action_to_add = action.copy()
|
|
202
202
|
action_to_add['id'] = current_len + 1
|
|
203
203
|
action_to_add['uuid'] = uuid.uuid4().fields[-1]
|
|
204
204
|
self.currently_editing_script.append(action_to_add)
|
|
205
|
-
self.
|
|
205
|
+
self._insert_action(insert_position, current_len)
|
|
206
206
|
self.update_time_stamp()
|
|
207
207
|
|
|
208
|
-
def add_variable(self, statement, variable, type):
|
|
208
|
+
def add_variable(self, statement, variable, type, insert_position=None):
|
|
209
209
|
variable = self.validate_function_name(variable)
|
|
210
210
|
convert_type = getattr(builtins, type)
|
|
211
211
|
statement = convert_type(statement)
|
|
212
212
|
current_len = len(self.currently_editing_script)
|
|
213
213
|
uid = uuid.uuid4().fields[-1]
|
|
214
|
-
|
|
214
|
+
action = {"id": current_len + 1, "instrument": 'variable', "action": variable,
|
|
215
215
|
"args": {"statement": 'None' if statement == '' else statement}, "return": '', "uuid": uid,
|
|
216
|
-
"arg_types": {"statement": type}}
|
|
217
|
-
self.currently_editing_script.
|
|
218
|
-
self.
|
|
216
|
+
"arg_types": {"statement": type}}
|
|
217
|
+
self.currently_editing_script.append(action)
|
|
218
|
+
self._insert_action(insert_position, current_len)
|
|
219
219
|
self.update_time_stamp()
|
|
220
220
|
|
|
221
|
+
def _insert_action(self, insert_position, current_len, action_len:int=1):
|
|
222
|
+
|
|
223
|
+
if insert_position is None:
|
|
224
|
+
self.currently_editing_order.extend([str(current_len + i + 1) for i in range(action_len)])
|
|
225
|
+
else:
|
|
226
|
+
index = int(insert_position) - 1
|
|
227
|
+
self.currently_editing_order[index:index] = [str(current_len + i + 1) for i in range(action_len)]
|
|
228
|
+
self.sort_actions()
|
|
229
|
+
|
|
221
230
|
def get_added_variables(self):
|
|
222
231
|
added_variables: Dict[str, str] = {action["action"]: action["arg_types"]["statement"] for action in
|
|
223
232
|
self.currently_editing_script if action["instrument"] == "variable"}
|
|
@@ -251,7 +260,7 @@ class Script(db.Model):
|
|
|
251
260
|
kwargs[key] = f"#{self.validate_function_name(value[1:])}"
|
|
252
261
|
return kwargs
|
|
253
262
|
|
|
254
|
-
def add_logic_action(self, logic_type: str, statement):
|
|
263
|
+
def add_logic_action(self, logic_type: str, statement, insert_position=None):
|
|
255
264
|
current_len = len(self.currently_editing_script)
|
|
256
265
|
uid = uuid.uuid4().fields[-1]
|
|
257
266
|
logic_dict = {
|
|
@@ -291,7 +300,7 @@ class Script(db.Model):
|
|
|
291
300
|
}
|
|
292
301
|
action_list = logic_dict[logic_type]
|
|
293
302
|
self.currently_editing_script.extend(action_list)
|
|
294
|
-
self.
|
|
303
|
+
self._insert_action(insert_position, current_len, len(action_list))
|
|
295
304
|
self.update_time_stamp()
|
|
296
305
|
|
|
297
306
|
def delete_action(self, id: int):
|
|
@@ -446,7 +455,8 @@ class Script(db.Model):
|
|
|
446
455
|
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
447
456
|
config_type.items()]
|
|
448
457
|
|
|
449
|
-
|
|
458
|
+
script_type = f"_{stype}" if stype != "script" else ""
|
|
459
|
+
function_header = f"def {run_name}{script_type}("
|
|
450
460
|
|
|
451
461
|
if stype == "script":
|
|
452
462
|
function_header += ", ".join(configure)
|
|
@@ -492,6 +502,9 @@ class Script(db.Model):
|
|
|
492
502
|
return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
|
|
493
503
|
elif instrument == 'repeat':
|
|
494
504
|
return self._process_repeat(indent_unit, action_name, statement, next_action)
|
|
505
|
+
#todo
|
|
506
|
+
# elif instrument == 'registered_workflows':
|
|
507
|
+
# return inspect.getsource(my_function)
|
|
495
508
|
else:
|
|
496
509
|
return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
|
|
497
510
|
|
ivoryos/utils/form.py
CHANGED
|
@@ -7,6 +7,10 @@ from flask_wtf import FlaskForm
|
|
|
7
7
|
from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
|
|
8
8
|
import inspect
|
|
9
9
|
|
|
10
|
+
from ivoryos.utils.db_models import Script
|
|
11
|
+
from ivoryos.utils.global_config import GlobalConfig
|
|
12
|
+
|
|
13
|
+
global_config = GlobalConfig()
|
|
10
14
|
|
|
11
15
|
def find_variable(data, script):
|
|
12
16
|
"""
|
|
@@ -345,6 +349,13 @@ def create_form_from_action(action: dict, script=None, design=True):
|
|
|
345
349
|
setattr(DynamicForm, 'return', return_value)
|
|
346
350
|
return DynamicForm()
|
|
347
351
|
|
|
352
|
+
def create_all_builtin_forms(script):
|
|
353
|
+
all_builtin_forms = {}
|
|
354
|
+
for logic_name in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
355
|
+
# signature = info.get('signature', {})
|
|
356
|
+
form_class = create_builtin_form(logic_name, script)
|
|
357
|
+
all_builtin_forms[logic_name] = form_class()
|
|
358
|
+
return all_builtin_forms
|
|
348
359
|
|
|
349
360
|
def create_builtin_form(logic_type, script):
|
|
350
361
|
"""
|
|
@@ -379,14 +390,51 @@ def create_builtin_form(logic_type, script):
|
|
|
379
390
|
render_kw=render_kwargs)
|
|
380
391
|
type_field = SelectField(
|
|
381
392
|
'Select Input Type',
|
|
382
|
-
choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String')],
|
|
393
|
+
choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String'), ('bool', 'Boolean')],
|
|
383
394
|
default='str' # Optional default value
|
|
384
395
|
)
|
|
385
396
|
setattr(BuiltinFunctionForm, "variable", variable_field)
|
|
386
397
|
setattr(BuiltinFunctionForm, "type", type_field)
|
|
387
398
|
hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
|
|
388
399
|
setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
|
|
389
|
-
return BuiltinFunctionForm
|
|
400
|
+
return BuiltinFunctionForm
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_method_from_workflow(function_string):
|
|
404
|
+
"""Creates a function from a string and assigns it a new name."""
|
|
405
|
+
|
|
406
|
+
namespace = {}
|
|
407
|
+
exec(function_string, globals(), namespace) # Execute the string in a safe namespace
|
|
408
|
+
func_name = next(iter(namespace))
|
|
409
|
+
# Get the function name dynamically
|
|
410
|
+
return namespace[func_name]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def create_workflow_forms(script, autofill: bool = False, design: bool = False):
|
|
414
|
+
workflow_forms = {}
|
|
415
|
+
functions = {}
|
|
416
|
+
class RegisteredWorkflows:
|
|
417
|
+
pass
|
|
418
|
+
|
|
419
|
+
deck_name = script.deck
|
|
420
|
+
workflows = Script.query.filter(Script.deck==deck_name, Script.name != script.name).all()
|
|
421
|
+
for workflow in workflows:
|
|
422
|
+
compiled_strs = workflow.compile().get('script', "")
|
|
423
|
+
method = get_method_from_workflow(compiled_strs)
|
|
424
|
+
functions[workflow.name] = dict(signature=inspect.signature(method), docstring=inspect.getdoc(method))
|
|
425
|
+
setattr(RegisteredWorkflows, workflow.name, method)
|
|
426
|
+
|
|
427
|
+
form_class = create_form_for_method(method, autofill, script, design)
|
|
428
|
+
|
|
429
|
+
hidden_method_name = HiddenField(name=f'hidden_name', description="",
|
|
430
|
+
render_kw={"value": f'{workflow.name}'})
|
|
431
|
+
if design:
|
|
432
|
+
return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
|
|
433
|
+
setattr(form_class, 'return', return_value)
|
|
434
|
+
setattr(form_class, 'workflow_name', hidden_method_name)
|
|
435
|
+
workflow_forms[workflow.name] = form_class()
|
|
436
|
+
global_config.registered_workflows = RegisteredWorkflows
|
|
437
|
+
return workflow_forms, functions
|
|
390
438
|
|
|
391
439
|
|
|
392
440
|
def create_action_button(script, stype=None):
|
ivoryos/utils/global_config.py
CHANGED
|
@@ -8,6 +8,7 @@ class GlobalConfig:
|
|
|
8
8
|
if cls._instance is None:
|
|
9
9
|
cls._instance = super(GlobalConfig, cls).__new__(cls, *args, **kwargs)
|
|
10
10
|
cls._instance._deck = None
|
|
11
|
+
cls._instance._registered_workflows = None
|
|
11
12
|
cls._instance._agent = None
|
|
12
13
|
cls._instance._defined_variables = {}
|
|
13
14
|
cls._instance._api_variables = set()
|
|
@@ -24,6 +25,15 @@ class GlobalConfig:
|
|
|
24
25
|
if self._deck is None:
|
|
25
26
|
self._deck = value
|
|
26
27
|
|
|
28
|
+
@property
|
|
29
|
+
def registered_workflows(self):
|
|
30
|
+
return self._registered_workflows
|
|
31
|
+
|
|
32
|
+
@registered_workflows.setter
|
|
33
|
+
def registered_workflows(self, value):
|
|
34
|
+
if self._registered_workflows is None:
|
|
35
|
+
self._registered_workflows = value
|
|
36
|
+
|
|
27
37
|
|
|
28
38
|
@property
|
|
29
39
|
def deck_snapshot(self):
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -24,9 +24,11 @@ class ScriptRunner:
|
|
|
24
24
|
self.stop_current_event = threading.Event()
|
|
25
25
|
self.is_running = False
|
|
26
26
|
self.lock = threading.Lock()
|
|
27
|
+
self.paused = False
|
|
27
28
|
|
|
28
29
|
def toggle_pause(self):
|
|
29
30
|
"""Toggles between pausing and resuming the script"""
|
|
31
|
+
self.paused = not self.paused
|
|
30
32
|
if self.pause_event.is_set():
|
|
31
33
|
self.pause_event.clear() # Pause the script
|
|
32
34
|
return "Paused"
|
|
@@ -34,10 +36,15 @@ class ScriptRunner:
|
|
|
34
36
|
self.pause_event.set() # Resume the script
|
|
35
37
|
return "Resumed"
|
|
36
38
|
|
|
39
|
+
def pause_status(self):
|
|
40
|
+
"""Toggles between pausing and resuming the script"""
|
|
41
|
+
return self.paused
|
|
42
|
+
|
|
37
43
|
def reset_stop_event(self):
|
|
38
44
|
"""Resets the stop event"""
|
|
39
45
|
self.stop_pending_event.clear()
|
|
40
46
|
self.stop_current_event.clear()
|
|
47
|
+
self.pause_event.set()
|
|
41
48
|
|
|
42
49
|
def abort_pending(self):
|
|
43
50
|
"""Abort the pending iteration after the current is finished"""
|
|
@@ -68,7 +75,7 @@ class ScriptRunner:
|
|
|
68
75
|
thread.start()
|
|
69
76
|
return thread
|
|
70
77
|
|
|
71
|
-
def
|
|
78
|
+
def exex_steps(self, script, section_name, logger, socketio, **kwargs):
|
|
72
79
|
"""
|
|
73
80
|
Executes a function defined in a string line by line.
|
|
74
81
|
|
|
@@ -76,73 +83,89 @@ class ScriptRunner:
|
|
|
76
83
|
:param kwargs: Arguments to pass to the function
|
|
77
84
|
:return: The final result of the function execution
|
|
78
85
|
"""
|
|
86
|
+
_func_str = script.compile()
|
|
87
|
+
step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
|
|
79
88
|
global deck
|
|
80
89
|
if deck is None:
|
|
81
90
|
deck = global_config.deck
|
|
82
91
|
# func_str = script.compile()
|
|
83
92
|
# Parse function body from string
|
|
84
|
-
|
|
93
|
+
temp_connections = global_config.defined_variables
|
|
85
94
|
# Prepare execution environment
|
|
86
|
-
exec_globals = {"deck": deck} # Add required global objects
|
|
95
|
+
exec_globals = {"deck": deck, "time":time} # Add required global objects
|
|
96
|
+
# exec_globals = {"deck": deck, "time": time, "registered_workflows":registered_workflows} # Add required global objects
|
|
97
|
+
exec_globals.update(temp_connections)
|
|
87
98
|
exec_locals = {} # Local execution scope
|
|
88
99
|
|
|
89
100
|
# Define function arguments manually in exec_locals
|
|
90
101
|
exec_locals.update(kwargs)
|
|
102
|
+
index = 0
|
|
91
103
|
|
|
92
104
|
# Execute each line dynamically
|
|
93
|
-
|
|
105
|
+
while index < len(step_list):
|
|
94
106
|
if self.stop_current_event.is_set():
|
|
95
107
|
logger.info(f'Stopping execution during {section_name}')
|
|
96
108
|
break
|
|
109
|
+
line = step_list[index]
|
|
97
110
|
logger.info(f"Executing: {line}") # Debugging output
|
|
98
111
|
socketio.emit('execution', {'section': f"{section_name}-{index}"})
|
|
99
112
|
# self._emit_progress(socketio, 100)
|
|
100
|
-
|
|
113
|
+
try:
|
|
114
|
+
exec(line, exec_globals, exec_locals)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Error during script execution: {e}")
|
|
117
|
+
socketio.emit('error', {'message': e.__str__()})
|
|
118
|
+
self.toggle_pause()
|
|
101
119
|
self.pause_event.wait()
|
|
102
120
|
|
|
121
|
+
# todo update script during the run
|
|
122
|
+
# _func_str = script.compile()
|
|
123
|
+
# step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
|
|
124
|
+
index += 1
|
|
103
125
|
return exec_locals # Return the 'results' variable
|
|
104
126
|
|
|
105
127
|
def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
|
|
106
128
|
output_path):
|
|
107
129
|
time.sleep(1)
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
# _func_str = script.compile()
|
|
131
|
+
# step_list_dict: dict = script.convert_to_lines(_func_str)
|
|
110
132
|
self._emit_progress(socketio, 1)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
|
|
134
|
+
# Run "prep" section once
|
|
135
|
+
script_dict = script.script_dict
|
|
136
|
+
self._run_actions(script, section_name="prep", logger=logger, socketio=socketio)
|
|
137
|
+
output_list = []
|
|
138
|
+
_, arg_type = script.config("script")
|
|
139
|
+
_, return_list = script.config_return()
|
|
140
|
+
|
|
141
|
+
# Run "script" section multiple times
|
|
142
|
+
if repeat_count:
|
|
143
|
+
self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, script,
|
|
144
|
+
run_name, return_list, logger, socketio)
|
|
145
|
+
elif config:
|
|
146
|
+
self._run_config_section(config, arg_type, output_list, script, run_name, logger,
|
|
147
|
+
socketio)
|
|
148
|
+
|
|
149
|
+
# Run "cleanup" section once
|
|
150
|
+
self._run_actions(script, section_name="cleanup", logger=logger, socketio=socketio)
|
|
151
|
+
# Reset the running flag when done
|
|
152
|
+
with self.lock:
|
|
153
|
+
self.is_running = False
|
|
154
|
+
# Save results if necessary
|
|
155
|
+
if output_list:
|
|
156
|
+
self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
|
|
157
|
+
self._emit_progress(socketio, 100)
|
|
158
|
+
|
|
159
|
+
def _run_actions(self, script, section_name="", logger=None, socketio=None):
|
|
160
|
+
_func_str = script.compile()
|
|
161
|
+
step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
|
|
162
|
+
logger.info(f'Executing {section_name} steps') if step_list else logger.info(f'No {section_name} steps')
|
|
140
163
|
if self.stop_pending_event.is_set():
|
|
141
164
|
logger.info(f"Stopping execution during {section_name} section.")
|
|
142
165
|
return
|
|
143
|
-
self.
|
|
166
|
+
self.exex_steps(script, section_name, logger, socketio)
|
|
144
167
|
|
|
145
|
-
def _run_config_section(self, config, arg_type, output_list,
|
|
168
|
+
def _run_config_section(self, config, arg_type, output_list, script, run_name, logger, socketio):
|
|
146
169
|
compiled = True
|
|
147
170
|
for i in config:
|
|
148
171
|
try:
|
|
@@ -162,12 +185,12 @@ class ScriptRunner:
|
|
|
162
185
|
self._emit_progress(socketio, progress)
|
|
163
186
|
# fname = f"{run_name}_script"
|
|
164
187
|
# function = self.globals_dict[fname]
|
|
165
|
-
output = self.
|
|
188
|
+
output = self.exex_steps(script, "script", logger, socketio, **kwargs)
|
|
166
189
|
if output:
|
|
167
190
|
# kwargs.update(output)
|
|
168
191
|
output_list.append(output)
|
|
169
192
|
|
|
170
|
-
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list,
|
|
193
|
+
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, script, run_name, return_list,
|
|
171
194
|
logger, socketio):
|
|
172
195
|
if bo_args:
|
|
173
196
|
logger.info('Initializing optimizer...')
|
|
@@ -185,7 +208,7 @@ class ScriptRunner:
|
|
|
185
208
|
logger.info(f'Output value: {parameters}')
|
|
186
209
|
# fname = f"{run_name}_script"
|
|
187
210
|
# function = self.globals_dict[fname]
|
|
188
|
-
output = self.
|
|
211
|
+
output = self.exex_steps(script, "script", logger, socketio, **parameters)
|
|
189
212
|
|
|
190
213
|
_output = {key: value for key, value in output.items() if key in return_list}
|
|
191
214
|
ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
|
|
@@ -196,7 +219,7 @@ class ScriptRunner:
|
|
|
196
219
|
else:
|
|
197
220
|
# fname = f"{run_name}_script"
|
|
198
221
|
# function = self.globals_dict[fname]
|
|
199
|
-
output = self.
|
|
222
|
+
output = self.exex_steps(script, "script", logger, socketio)
|
|
200
223
|
|
|
201
224
|
if output:
|
|
202
225
|
output_list.append(output)
|
ivoryos/utils/utils.py
CHANGED
|
@@ -97,7 +97,7 @@ def _inspect_class(class_object=None, debug=False):
|
|
|
97
97
|
under_score = "_"
|
|
98
98
|
if debug:
|
|
99
99
|
under_score = "__"
|
|
100
|
-
for function, method in inspect.getmembers(type(class_object), predicate=
|
|
100
|
+
for function, method in inspect.getmembers(type(class_object), predicate=callable):
|
|
101
101
|
if not function.startswith(under_score) and not function.isupper():
|
|
102
102
|
try:
|
|
103
103
|
annotation = inspect.signature(method)
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.21"
|