ivoryos 1.2.5__py3-none-any.whl → 1.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +16 -246
  3. ivoryos/app.py +154 -0
  4. ivoryos/optimizer/ax_optimizer.py +55 -28
  5. ivoryos/optimizer/base_optimizer.py +20 -1
  6. ivoryos/optimizer/baybe_optimizer.py +27 -17
  7. ivoryos/optimizer/nimo_optimizer.py +173 -0
  8. ivoryos/optimizer/registry.py +3 -1
  9. ivoryos/routes/auth/auth.py +35 -8
  10. ivoryos/routes/auth/templates/change_password.html +32 -0
  11. ivoryos/routes/control/control.py +58 -28
  12. ivoryos/routes/control/control_file.py +12 -15
  13. ivoryos/routes/control/control_new_device.py +21 -11
  14. ivoryos/routes/control/templates/controllers.html +27 -0
  15. ivoryos/routes/control/utils.py +2 -0
  16. ivoryos/routes/data/data.py +110 -44
  17. ivoryos/routes/data/templates/components/step_card.html +78 -13
  18. ivoryos/routes/data/templates/workflow_view.html +343 -113
  19. ivoryos/routes/design/design.py +59 -10
  20. ivoryos/routes/design/design_file.py +3 -3
  21. ivoryos/routes/design/design_step.py +43 -17
  22. ivoryos/routes/design/templates/components/action_form.html +2 -2
  23. ivoryos/routes/design/templates/components/canvas_main.html +6 -1
  24. ivoryos/routes/design/templates/components/edit_action_form.html +18 -3
  25. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  26. ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
  27. ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
  28. ivoryos/routes/design/templates/experiment_builder.html +3 -0
  29. ivoryos/routes/execute/execute.py +82 -22
  30. ivoryos/routes/execute/templates/components/logging_panel.html +50 -25
  31. ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
  32. ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
  33. ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
  34. ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
  35. ivoryos/routes/execute/templates/experiment_run.html +0 -264
  36. ivoryos/routes/library/library.py +9 -11
  37. ivoryos/routes/main/main.py +30 -2
  38. ivoryos/server.py +180 -0
  39. ivoryos/socket_handlers.py +1 -1
  40. ivoryos/static/ivoryos_logo.png +0 -0
  41. ivoryos/static/js/action_handlers.js +259 -88
  42. ivoryos/static/js/socket_handler.js +40 -5
  43. ivoryos/static/js/sortable_design.js +29 -11
  44. ivoryos/templates/base.html +61 -2
  45. ivoryos/utils/bo_campaign.py +18 -17
  46. ivoryos/utils/client_proxy.py +267 -36
  47. ivoryos/utils/db_models.py +286 -60
  48. ivoryos/utils/decorators.py +34 -0
  49. ivoryos/utils/form.py +52 -19
  50. ivoryos/utils/global_config.py +21 -0
  51. ivoryos/utils/nest_script.py +314 -0
  52. ivoryos/utils/py_to_json.py +80 -10
  53. ivoryos/utils/script_runner.py +573 -189
  54. ivoryos/utils/task_runner.py +69 -22
  55. ivoryos/utils/utils.py +48 -5
  56. ivoryos/version.py +1 -1
  57. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/METADATA +109 -47
  58. ivoryos-1.4.4.dist-info/RECORD +119 -0
  59. ivoryos-1.4.4.dist-info/top_level.txt +3 -0
  60. tests/__init__.py +0 -0
  61. tests/conftest.py +133 -0
  62. tests/integration/__init__.py +0 -0
  63. tests/integration/test_route_auth.py +80 -0
  64. tests/integration/test_route_control.py +94 -0
  65. tests/integration/test_route_database.py +61 -0
  66. tests/integration/test_route_design.py +36 -0
  67. tests/integration/test_route_main.py +35 -0
  68. tests/integration/test_sockets.py +26 -0
  69. tests/unit/test_type_conversion.py +42 -0
  70. tests/unit/test_util.py +3 -0
  71. ivoryos/routes/api/api.py +0 -56
  72. ivoryos-1.2.5.dist-info/RECORD +0 -100
  73. ivoryos-1.2.5.dist-info/top_level.txt +0 -1
  74. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +0 -0
  75. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/licenses/LICENSE +0 -0
@@ -4,127 +4,357 @@
4
4
 
5
5
  {% block body %}
6
6
  <style>
7
- .vis-time-axis .vis-text.vis-minor,
8
- .vis-time-axis .vis-text.vis-major {
9
- color: #666;
10
- }
11
- .vis-item.stop {
12
- background-color: red;
13
- color: white;
14
- border: none;
15
- font-weight: bold;
16
- }
7
+ .vis-time-axis .vis-text.vis-minor,
8
+ .vis-time-axis .vis-text.vis-major {
9
+ color: #666;
10
+ }
11
+ .vis-item.stop {
12
+ background-color: #dc3545;
13
+ color: white;
14
+ border: none;
15
+ font-weight: bold;
16
+ }
17
+ .vis-item.prep {
18
+ background-color: #17a2b8;
19
+ border-color: #138496;
20
+ }
21
+ .vis-item.script {
22
+ background-color: #28a745;
23
+ border-color: #1e7e34;
24
+ }
25
+ .vis-item.cleanup {
26
+ background-color: #ffc107;
27
+ border-color: #d39e00;
28
+ color: #212529;
29
+ }
30
+ #visualization {
31
+ border: 1px solid #dee2e6;
32
+ border-radius: 0.375rem;
33
+ background-color: #fff;
34
+ min-height: 200px;
35
+ }
36
+
37
+ .section-header {
38
+ border-bottom: 2px solid #dee2e6;
39
+ padding-bottom: 0.5rem;
40
+ margin-bottom: 1rem;
41
+ color: #495057;
42
+ }
43
+ .loading-spinner {
44
+ display: none;
45
+ }
17
46
  </style>
18
47
 
19
- <div id="timeline"></div>
20
48
 
49
+ <div class="timeline-section" style="margin-bottom: 2rem;">
50
+ <h3 class="section-header">
51
+ <i class="fas fa-clock me-2"></i>Execution Timeline
52
+ </h3>
53
+ <div class="alert alert-info" role="alert">
54
+ <i class="fas fa-info-circle me-2"></i>
55
+ <strong>Tip:</strong> Click on timeline items to navigate to detailed views. Use Ctrl+scroll to zoom.
56
+ </div>
57
+ <div id="visualization"></div>
58
+ </div>
59
+
60
+ <!-- Phase Output Plot Section -->
61
+
62
+ <div class="data-section" style="margin-bottom: 2rem;">
63
+ <h3 class="section-header">
64
+ <div class="col-md-6">
65
+ <label for="output-select" class="form-label">Select Data Type:</label>
66
+ <select id="output-select" class="form-select">
67
+ <option value="">Loading data types...</option>
68
+ </select>
69
+ </div>
70
+ </h3>
71
+
72
+ <div class="plot-controls">
73
+ <div>
74
+ <div class="col-md-6 text-md-end">
75
+ <div class="loading-spinner">
76
+ <div class="spinner-border spinner-border-sm text-primary me-2" role="status">
77
+ <span class="visually-hidden">Loading...</span>
78
+ </div>
79
+ Loading plot data...
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ <div id="phase-plot" style="height:450px;" class="border rounded bg-white shadow-sm"></div>
85
+ </div>
86
+
87
+ <!-- Workflow Details Section -->
88
+
89
+ <div>
90
+ <h2 class="section-header">
91
+ <i class="fas fa-project-diagram me-2"></i>Workflow: {{ workflow.name }}
92
+ </h2>
93
+
94
+ <!-- Prep Phase -->
95
+ {% if grouped.prep %}
96
+ <div class="mb-4">
97
+ <h4 class="text-info mb-3">
98
+ <i class="fas fa-tools me-2"></i>Prep
99
+ </h4>
100
+ <div class="row">
101
+ {% for phase in grouped.prep %}
102
+ <div class="col-lg-6 mb-3">
103
+ {% include "components/step_card.html" %}
104
+ </div>
105
+ {% endfor %}
106
+ </div>
107
+ </div>
108
+ {% endif %}
109
+
110
+ <!-- Script Iterations -->
111
+ {% for repeat_index, phase_list in grouped.script.items()|sort %}
112
+ <div class="mb-4" id="card-iter{{ repeat_index }}">
113
+ <h4 class="text-success mb-3">
114
+ <i class="fas fa-redo me-2"></i>Iteration {{ repeat_index }}
115
+ </h4>
116
+ <div class="row">
117
+ {% for phase in phase_list %}
118
+ <div class="col-lg-6 mb-3">
119
+ {% include "components/step_card.html" %}
120
+ </div>
121
+ {% endfor %}
122
+ </div>
123
+ </div>
124
+ {% endfor %}
125
+
126
+ <!-- Cleanup Phase -->
127
+ {% if grouped.cleanup %}
128
+ <div>
129
+ <h4 class="text-warning mb-3">
130
+ <i class="fas fa-broom me-2"></i>Cleanup
131
+ </h4>
132
+ <div class="row">
133
+ {% for phase in grouped.cleanup %}
134
+ <div class="col-lg-6 mb-3">
135
+ {% include "components/step_card.html" %}
136
+ </div>
137
+ {% endfor %}
138
+ </div>
139
+ </div>
140
+ {% endif %}
141
+ </div>
142
+
143
+
144
+
145
+ <!-- External Dependencies -->
21
146
  <script src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script>
22
147
  <link href="https://unpkg.com/vis-timeline@latest/styles/vis-timeline-graph2d.min.css" rel="stylesheet"/>
148
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
23
149
 
24
- <h1>Experiment Step View</h1>
150
+ <script type="text/javascript">
151
+ document.addEventListener('DOMContentLoaded', function() {
152
+ // ---------------- Timeline Setup ----------------
153
+ const container = document.getElementById('visualization');
154
+ const groups = [
155
+ { id: 'all', content: 'Workflow Execution' }
156
+ ];
25
157
 
26
- <div id="visualization"></div>
158
+ const items = [
159
+ {% if grouped.prep %}
160
+ {
161
+ id: 'prep',
162
+ content: 'Prep',
163
+ start: '{{ grouped.prep[0].start_time }}',
164
+ end: '{{ grouped.prep[-1].end_time }}',
165
+ className: 'prep',
166
+ group: 'all',
167
+ },
168
+ {% endif %}
27
169
 
28
- <script type="text/javascript">
29
- var container = document.getElementById('visualization');
30
-
31
- const items = [
32
- {% if grouped.prep %}
33
- {
34
- id: 'prep',
35
- content: 'Prep Phase',
36
- start: '{{ grouped.prep[0].start_time }}',
37
- end: '{{ grouped.prep[-1].end_time }}',
38
- className: 'prep',
39
- group: 'prep'
40
- },
41
- {% endif %}
42
-
43
- {% for repeat_index, step_list in grouped.script.items()|sort %}
44
- {
45
- id: 'iter{{ repeat_index }}',
46
- content: 'Iteration {{ repeat_index }}',
47
- start: '{{ step_list[0].start_time }}',
48
- end: '{{ step_list[-1].end_time }}',
49
- className: 'script',
50
- group: 'iter{{ repeat_index }}'
51
- },
52
- {% for step in step_list %}
53
- {% if step.method_name == "stop" %}
54
- {
55
- id: 'stop-{{ step.id }}',
56
- content: '🛑 Stop',
57
- start: '{{ step.start_time }}',
58
- type: 'point',
59
- className: 'stop',
60
- group: 'iter{{ repeat_index }}'
61
- },
62
- {% endif %}
63
- {% endfor %}
64
- {% endfor %}
65
-
66
- {% if grouped.cleanup %}
67
- {
68
- id: 'cleanup',
69
- content: 'Cleanup Phase',
70
- start: '{{ grouped.cleanup[0].start_time }}',
71
- end: '{{ grouped.cleanup[-1].end_time }}',
72
- className: 'cleanup',
73
- group: 'cleanup'
74
-
75
- },
76
- {% endif %}
77
- ];
78
-
79
- const groups = [
80
- {% if grouped.prep %}{ id: 'prep', content: 'Prep' },{% endif %}
81
- {% for repeat_index in grouped.script.keys()|sort %}{ id: 'iter{{ repeat_index }}', content: 'Iteration {{ repeat_index }}' },{% endfor %}
82
- {% if grouped.cleanup %}{ id: 'cleanup', content: 'Cleanup' },{% endif %}
83
- ];
84
-
85
- var options = {
86
- clickToUse: true,
87
- stack: false, // important to keep point within group row
88
- horizontalScroll: true,
89
- zoomKey: 'ctrlKey'
90
- };
91
-
92
- // Initialize your timeline with the sorted groups
93
- const timeline = new vis.Timeline(container, items, groups, options);
94
-
95
- timeline.on('select', function (props) {
96
- const id = props.items[0];
97
- if (id && id.startsWith('iter')) {
98
- const card = document.getElementById('card-' + id);
99
- if (card) {
100
- const yOffset = -80;
101
- const y = card.getBoundingClientRect().top + window.pageYOffset + yOffset;
102
- window.scrollTo({ top: y, behavior: 'smooth' });
103
- }
104
- }
105
- });
170
+ {% for repeat_index, step_list in grouped.script.items()|sort %}
171
+ {
172
+ id: 'iter{{ repeat_index }}',
173
+ content: 'Iteration {{ repeat_index }}',
174
+ start: '{{ step_list[0].start_time }}',
175
+ end: '{{ step_list[-1].end_time }}',
176
+ className: 'script',
177
+ group: 'all',
178
+ },
179
+ {% for step in step_list %}
180
+ {% if step.method_name == "stop" %}
181
+ {
182
+ id: 'stop-{{ step.id }}',
183
+ content: '🛑 Stop',
184
+ start: '{{ step.start_time }}',
185
+ type: 'point',
186
+ className: 'stop',
187
+ group: 'all',
188
+ title: 'Stop event at {{ step.start_time }}'
189
+ },
190
+ {% endif %}
191
+ {% endfor %}
192
+ {% endfor %}
193
+
194
+ {% if grouped.cleanup %}
195
+ {
196
+ id: 'cleanup',
197
+ content: 'Cleanup ',
198
+ start: '{{ grouped.cleanup[0].start_time }}',
199
+ end: '{{ grouped.cleanup[-1].end_time }}',
200
+ className: 'cleanup',
201
+ group: 'all',
202
+ },
203
+ {% endif %}
204
+ ];
205
+
206
+ var timeline = new vis.Timeline(container, items, groups, {
207
+ clickToUse: true,
208
+ stack: false, // keep items from overlapping vertically
209
+ horizontalScroll: true,
210
+ zoomKey: 'ctrlKey'
211
+ });
212
+
213
+ timeline.on('select', function (props) {
214
+ const id = props.items[0];
215
+ if (id && id.startsWith('iter')) {
216
+ const card = document.getElementById('card-' + id);
217
+ if (card) {
218
+ const yOffset = -80;
219
+ const y = card.getBoundingClientRect().top + window.pageYOffset + yOffset;
220
+ window.scrollTo({ top: y, behavior: 'smooth' });
221
+ }
222
+ }
223
+ });
224
+
225
+ // ---------------- Phase Data Plot ----------------
226
+ const loadingSpinner = document.querySelector('.loading-spinner');
227
+ const select = document.getElementById('output-select');
228
+
229
+ loadingSpinner.style.display = 'block';
230
+
231
+
232
+ fetch("{{ url_for('data.workflow_phase_data', workflow_id=workflow.id) }}")
233
+ .then(res => {
234
+ if (!res.ok) {
235
+ throw new Error(`HTTP error! status: ${res.status}`);
236
+ }
237
+ return res.json();
238
+ })
239
+ .then(data => {
240
+ loadingSpinner.style.display = 'none';
241
+
242
+ const repeatKeys = Object.keys(data).sort((a, b) => a - b);
243
+ const dataSection = document.querySelector('.data-section'); // Get the entire section
244
+
245
+ if (!repeatKeys.length) {
246
+ // Hide the entire data section if no data
247
+ dataSection.style.display = 'none';
248
+ return;
249
+ }
250
+
251
+ const allKeys = new Set();
252
+ repeatKeys.forEach(k => {
253
+ Object.keys(data[k]).forEach(key => allKeys.add(key));
254
+ });
255
+
256
+ // If no keys found, also hide the section
257
+ if (allKeys.size === 0) {
258
+ dataSection.style.display = 'none';
259
+ return;
260
+ }
261
+
262
+ // Show the data section since we have data
263
+ dataSection.style.display = 'block';
264
+
265
+ // Clear and populate select options
266
+ select.innerHTML = '';
267
+ allKeys.forEach(k => {
268
+ const option = new Option(k, k);
269
+ select.appendChild(option);
270
+ });
271
+
272
+ function plotData(selectedKey) {
273
+ const x = [];
274
+ const y = [];
275
+
276
+ repeatKeys.forEach(repeat_index => {
277
+ const arr = data[repeat_index][selectedKey];
278
+ if (arr && arr.length) {
279
+ const batchCount = arr.length;
280
+
281
+ arr.forEach((d, batchIdx) => {
282
+ // Compute fractional x for batch indexing: e.g. 1.1, 1.2, 1.3
283
+ const xVal = parseFloat(repeat_index) + (batchIdx + 1) / (batchCount + 1);
284
+
285
+ if (typeof d === 'object' && d.x !== undefined && d.y !== undefined) {
286
+ x.push(xVal);
287
+ y.push(d.y);
288
+ } else if (typeof d === 'number') {
289
+ x.push(xVal);
290
+ y.push(d);
291
+ }
292
+ });
293
+ }
294
+ });
295
+
296
+ const trace = {
297
+ x: x,
298
+ y: y,
299
+ mode: 'markers',
300
+ name: selectedKey,
301
+ marker: { size: 8 },
302
+ line: { shape: 'linear', width: 1 },
303
+ };
304
+
305
+ const layout = {
306
+ xaxis: {
307
+ title: 'Trial (Batch Sub-index)',
308
+ gridcolor: '#e9ecef',
309
+ tickvals: repeatKeys.map(k => parseFloat(k)),
310
+ ticktext: repeatKeys.map(k => `Trial ${k}`),
311
+ },
312
+ yaxis: {
313
+ title: selectedKey,
314
+ gridcolor: '#e9ecef'
315
+ },
316
+ plot_bgcolor: '#ffffff',
317
+ paper_bgcolor: '#ffffff',
318
+ margin: { t: 60, r: 40, b: 60, l: 80 }
319
+ };
320
+
321
+ const config = {
322
+ responsive: true,
323
+ displayModeBar: true,
324
+ modeBarButtonsToRemove: ['lasso2d', 'select2d']
325
+ };
326
+
327
+ Plotly.newPlot('phase-plot', [trace], layout, config);
328
+ }
329
+
330
+ select.addEventListener('change', e => {
331
+ if (e.target.value) {
332
+ plotData(e.target.value);
333
+ }
334
+ });
335
+
336
+ // Plot first available data type
337
+ if (allKeys.size > 0) {
338
+ plotData([...allKeys][0]);
339
+ }
340
+ })
341
+ .catch(error => {
342
+ loadingSpinner.style.display = 'none';
343
+ console.error('Error loading phase data:', error);
344
+
345
+ const dataSection = document.querySelector('.data-section');
346
+ // Hide the section on error as well
347
+ dataSection.style.display = 'none';
348
+
349
+ // Optionally, you could show an error message instead:
350
+ // dataSection.innerHTML = `
351
+ // <div class="alert alert-danger m-3" role="alert">
352
+ // <i class="fas fa-exclamation-triangle me-2"></i>
353
+ // <strong>Error:</strong> Unable to load phase data. ${error.message}
354
+ // </div>
355
+ // `;
356
+ });
357
+ });
106
358
  </script>
107
359
 
108
- <h2>Workflow: {{ workflow.name }}</h2>
109
-
110
- {% if grouped.prep %}
111
- <h4 class="mt-4">Prep Phase</h4>
112
- {% for step in grouped.prep %}
113
- {% include "components/step_card.html" %}
114
- {% endfor %}
115
- {% endif %}
116
-
117
- {% for repeat_index, step_list in grouped.script.items()|sort %}
118
- <h4 class="mt-4" id="card-iter{{ repeat_index }}">Iteration {{ repeat_index }}</h4>
119
- {% for step in step_list %}
120
- {% include "components/step_card.html" %}
121
- {% endfor %}
122
- {% endfor %}
123
-
124
- {% if grouped.cleanup %}
125
- <h4 class="mt-4">Cleanup Phase</h4>
126
- {% for step in grouped.cleanup %}
127
- {% include "components/step_card.html" %}
128
- {% endfor %}
129
- {% endif %}
130
360
  {% endblock %}
@@ -35,6 +35,9 @@ def _create_forms(instrument, script, autofill, pseudo_deck = None):
35
35
  _object = global_config.defined_variables.get(instrument)
36
36
  functions = utils._inspect_class(_object)
37
37
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
38
+ elif instrument.startswith("blocks"):
39
+ forms = create_form_from_pseudo(pseudo=global_config.building_blocks[instrument], autofill=autofill, script=script)
40
+ functions = global_config.building_blocks[instrument]
38
41
  else:
39
42
  if deck:
40
43
  functions = global_config.deck_snapshot.get(instrument, {})
@@ -84,15 +87,39 @@ def experiment_builder():
84
87
 
85
88
  # edit_action_info = session.get("edit_action")
86
89
 
87
-
88
- exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
90
+ try:
91
+ exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
92
+ except Exception as e:
93
+ exec_string = {}
94
+ flash(f"Error in Python script: {e}")
89
95
  session['python_code'] = exec_string
90
96
 
91
97
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
92
98
 
93
99
  return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
94
100
  script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
95
- local_variables=global_config.defined_variables)
101
+ local_variables=global_config.defined_variables, block_variables=global_config.building_blocks)
102
+
103
+ @design.route("/draft/code_preview", methods=["GET"])
104
+ @login_required
105
+ def compile_preview():
106
+ # Get mode and batch from query parameters
107
+ script = utils.get_script_file()
108
+ mode = request.args.get("mode", "single") # default to "single"
109
+ batch = request.args.get("batch", "sample") # default to "sample"
110
+
111
+ try:
112
+ # Example: decide which code to return based on mode/batch
113
+ if mode == "single":
114
+ code = script.compile(current_app.config['SCRIPT_FOLDER'])
115
+ elif mode == "batch":
116
+ code = script.compile(current_app.config['SCRIPT_FOLDER'], batch=True, mode=batch)
117
+ else:
118
+ code = "Invalid mode. Please select 'single' or 'batch'."
119
+ except Exception as e:
120
+ code = f"Error compiling: {e}"
121
+ # print(code)
122
+ return jsonify(code=code)
96
123
 
97
124
 
98
125
  @design.route("/draft/meta", methods=["PATCH"])
@@ -192,7 +219,8 @@ def update_ui_state():
192
219
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
193
220
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
194
221
  html = render_template("components/sidebar.html", history=deck_list,
195
- defined_variables=deck_variables, local_variables = global_config.defined_variables)
222
+ defined_variables=deck_variables, local_variables = global_config.defined_variables,
223
+ block_variables=global_config.building_blocks)
196
224
  return jsonify({"html": html})
197
225
  return jsonify({"error": "Invalid request"}), 400
198
226
 
@@ -253,7 +281,7 @@ def submit_script():
253
281
  """
254
282
  .. :quickref: Workflow Design; convert Python to workflow script
255
283
 
256
- .. http:post:: /design/submit_python
284
+ .. http:post:: /draft/submit_python
257
285
 
258
286
  Convert a Python script to a workflow script and save it in the database.
259
287
 
@@ -310,26 +338,39 @@ def methods_handler(instrument: str = ''):
310
338
 
311
339
  success = True
312
340
  msg = ""
341
+ request.form
313
342
  if "hidden_name" in request.form:
343
+ deck_snapshot = global_config.deck_snapshot
344
+ block_snapshot = global_config.building_blocks
314
345
  method_name = request.form.get("hidden_name", None)
315
346
  form = forms.get(method_name) if forms else None
316
347
  insert_position = request.form.get("drop_target_id", None)
348
+
317
349
  if form:
318
350
  kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
319
351
  if form.validate_on_submit():
320
352
  function_name = kwargs.pop("hidden_name")
353
+ batch_action = kwargs.pop("batch_action", False)
321
354
  save_data = kwargs.pop('return', '')
322
355
  primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
323
356
 
324
357
  # todo
325
- print(primitive_arg_types)
358
+ # print(primitive_arg_types)
326
359
 
327
360
  script.eval_list(kwargs, primitive_arg_types)
328
361
  kwargs = script.validate_variables(kwargs)
362
+ coroutine = False
363
+ if instrument.startswith("deck") and deck_snapshot:
364
+ coroutine = deck_snapshot[instrument][function_name].get("coroutine", False)
365
+ elif instrument.startswith("blocks") and block_snapshot:
366
+ coroutine = block_snapshot[instrument][function_name].get("coroutine", False)
329
367
  action = {"instrument": instrument, "action": function_name,
330
368
  "args": kwargs,
331
369
  "return": save_data,
332
- 'arg_types': primitive_arg_types}
370
+ 'arg_types': primitive_arg_types,
371
+ "coroutine": coroutine,
372
+ "batch_action": batch_action,
373
+ }
333
374
  script.add_action(action=action, insert_position=insert_position)
334
375
  else:
335
376
  msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
@@ -374,7 +415,13 @@ def methods_handler(instrument: str = ''):
374
415
  success = False
375
416
  msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
376
417
  utils.post_script_file(script)
377
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
418
+ #TODO
419
+ try:
420
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
421
+ except Exception as e:
422
+ exec_string = {}
423
+ msg = f"Compilation failed: {str(e)}"
424
+ # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
378
425
  session['python_code'] = exec_string
379
426
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
380
427
  html = render_template("components/canvas_main.html", script=script, buttons_dict=design_buttons)
@@ -388,7 +435,7 @@ def get_operation_sidebar(instrument: str = ''):
388
435
  """
389
436
  .. :quickref: Workflow Design; handle methods of a specific instrument
390
437
 
391
- .. http:get:: /design/instruments/<string:instrument>
438
+ .. http:get:: /draft/instruments/<string:instrument>
392
439
 
393
440
  :param instrument: The name of the instrument to handle methods for.
394
441
  :type instrument: str
@@ -422,7 +469,9 @@ def get_operation_sidebar(instrument: str = ''):
422
469
  # edit_action_info = session.get("edit_action")
423
470
  html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
424
471
  defined_variables=deck_variables,
425
- local_variables=global_config.defined_variables)
472
+ local_variables=global_config.defined_variables,
473
+ block_variables=global_config.building_blocks,
474
+ )
426
475
  return jsonify({"html": html})
427
476
 
428
477
 
@@ -17,7 +17,7 @@ def load_json():
17
17
  .. http:post:: /files/script-json
18
18
 
19
19
  :form file: workflow design JSON file
20
- :status 302: load script json and then redirects to :http:get:`/ivoryos/design/draft`
20
+ :status 302: load script json and then redirects to :http:get:`/ivoryos/draft`
21
21
  """
22
22
 
23
23
  if request.method == "POST":
@@ -38,7 +38,7 @@ def download_python():
38
38
 
39
39
  .. http:post:: /files/script-python
40
40
 
41
- :status 302: redirects to :http:get:`/ivoryos/design/script/`
41
+ :status 302: redirects to :http:get:`/ivoryos/draft`
42
42
  """
43
43
  script = utils.get_script_file()
44
44
  run_name = script.name if script.name else "untitled"
@@ -53,7 +53,7 @@ def download_json():
53
53
 
54
54
  .. http:post:: /files/script-json
55
55
 
56
- :status 302: redirects to :http:get:`/ivoryos/design/script/`
56
+ :status 302: redirects to :http:get:`/ivoryos/draft`
57
57
  """
58
58
  script = utils.get_script_file()
59
59
  run_name = script.name if script.name else "untitled"