ivoryos 1.2.7__py3-none-any.whl → 1.3.0__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.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

@@ -1,13 +1,42 @@
1
- <div class="card mb-2 {{ 'border-danger text-danger bg-light' if step.run_error else 'border-secondary' }}">
2
- <div class="card-body p-2">
3
- <strong>{{ step.method_name | format_name }}</strong>
4
- <small class="text-muted">
5
- <i class="fas fa-play-circle me-1"></i> Start: {{ step.start_time.strftime('%H:%M:%S') if step.start_time else 'N/A' }}
6
- <i class="fas fa-stop-circle ms-2 me-1"></i> End: {{ step.end_time.strftime('%H:%M:%S') if step.end_time else 'N/A' }}
7
- <!-- {% if step.run_error %}
8
- <i class="fas fa-stop-circle ms-2 me-1"></i> Error: {{ step.run_error if step.run_error else 'N/A' }}
9
- {% endif %} -->
10
- </small>
11
- <!-- <small>Error: {{ step.run_error }}</small> -->
12
- </div>
13
- </div>
1
+ <div class="card mb-2 {{ 'border-danger text-danger bg-light' if phase.run_error else 'border-secondary' }}">
2
+ <div class="card-body p-2">
3
+ <small class="text-muted">
4
+ <i class="fas fa-play-circle me-1"></i> Start: {{ phase.start_time.strftime('%H:%M:%S') if phase.start_time else 'N/A' }}
5
+ <i class="fas fa-stop-circle ms-2 me-1"></i> End: {{ phase.end_time.strftime('%H:%M:%S') if phase.end_time else 'N/A' }}
6
+ </small>
7
+ {% if phase.parameters %}
8
+ <div class="mt-2">
9
+ <strong>Parameters: </strong>
10
+ {% for key, value in phase.parameters.items() %}
11
+ <span class="badge bg-secondary me-1">{{ key }}: {{ value }}</span>
12
+ {% endfor %}
13
+ </div>
14
+ {% endif %}
15
+ {% if phase.steps %}
16
+ <div class="mt-2">
17
+ <strong>Steps:</strong>
18
+ <ul class="mb-0">
19
+ {% for step in phase.steps %}
20
+ <li class="{{ 'text-danger' if step.run_error else '' }}">
21
+ {{ step.method_name }}
22
+ <small class="text-muted">
23
+ ({{ step.start_time.strftime('%H:%M:%S') if step.start_time else 'N/A' }} –
24
+ {{ step.end_time.strftime('%H:%M:%S') if step.end_time else 'N/A' }})
25
+ </small>
26
+ </li>
27
+ {% endfor %}
28
+ </ul>
29
+ </div>
30
+ {% endif %}
31
+ {% if phase.outputs %}
32
+ <div class="mt-1">
33
+ <strong>Outputs:</strong>
34
+ <ul class="mb-0">
35
+ {% for key, value in phase.outputs.items() %}
36
+ <li>{{ key }}: {{ value }}</li>
37
+ {% endfor %}
38
+ </ul>
39
+ </div>
40
+ {% endif %}
41
+ </div>
42
+ </div>
@@ -4,127 +4,348 @@
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
+ arr.forEach(d => {
280
+ if (typeof d === 'object' && d.x !== undefined && d.y !== undefined) {
281
+ x.push(d.x);
282
+ y.push(d.y);
283
+ } else if (typeof d === 'number') {
284
+ x.push(parseInt(repeat_index));
285
+ y.push(d);
286
+ }
287
+ });
288
+ }
289
+ });
290
+
291
+ const trace = {
292
+ x: x,
293
+ y: y,
294
+ mode: 'markers',
295
+ name: selectedKey,
296
+ };
297
+
298
+ const layout = {
299
+ xaxis: {
300
+ title: 'Iteration Index',
301
+ gridcolor: '#e9ecef'
302
+ },
303
+ yaxis: {
304
+ title: selectedKey,
305
+ gridcolor: '#e9ecef'
306
+ },
307
+ plot_bgcolor: '#ffffff',
308
+ paper_bgcolor: '#ffffff',
309
+ margin: { t: 60, r: 40, b: 60, l: 80 }
310
+ };
311
+
312
+ const config = {
313
+ responsive: true,
314
+ displayModeBar: true,
315
+ modeBarButtonsToRemove: ['lasso2d', 'select2d']
316
+ };
317
+
318
+ Plotly.newPlot('phase-plot', [trace], layout, config);
319
+ }
320
+
321
+ select.addEventListener('change', e => {
322
+ if (e.target.value) {
323
+ plotData(e.target.value);
324
+ }
325
+ });
326
+
327
+ // Plot first available data type
328
+ if (allKeys.size > 0) {
329
+ plotData([...allKeys][0]);
330
+ }
331
+ })
332
+ .catch(error => {
333
+ loadingSpinner.style.display = 'none';
334
+ console.error('Error loading phase data:', error);
335
+
336
+ const dataSection = document.querySelector('.data-section');
337
+ // Hide the section on error as well
338
+ dataSection.style.display = 'none';
339
+
340
+ // Optionally, you could show an error message instead:
341
+ // dataSection.innerHTML = `
342
+ // <div class="alert alert-danger m-3" role="alert">
343
+ // <i class="fas fa-exclamation-triangle me-2"></i>
344
+ // <strong>Error:</strong> Unable to load phase data. ${error.message}
345
+ // </div>
346
+ // `;
347
+ });
348
+ });
106
349
  </script>
107
350
 
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
351
  {% 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, {})
@@ -92,7 +95,7 @@ def experiment_builder():
92
95
 
93
96
  return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
94
97
  script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
95
- local_variables=global_config.defined_variables)
98
+ local_variables=global_config.defined_variables, block_variables=global_config.building_blocks)
96
99
 
97
100
 
98
101
  @design.route("/draft/meta", methods=["PATCH"])
@@ -192,7 +195,8 @@ def update_ui_state():
192
195
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
193
196
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
194
197
  html = render_template("components/sidebar.html", history=deck_list,
195
- defined_variables=deck_variables, local_variables = global_config.defined_variables)
198
+ defined_variables=deck_variables, local_variables = global_config.defined_variables,
199
+ block_variables=global_config.building_blocks)
196
200
  return jsonify({"html": html})
197
201
  return jsonify({"error": "Invalid request"}), 400
198
202
 
@@ -310,6 +314,7 @@ def methods_handler(instrument: str = ''):
310
314
 
311
315
  success = True
312
316
  msg = ""
317
+ request.form
313
318
  if "hidden_name" in request.form:
314
319
  method_name = request.form.get("hidden_name", None)
315
320
  form = forms.get(method_name) if forms else None
@@ -322,7 +327,7 @@ def methods_handler(instrument: str = ''):
322
327
  primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
323
328
 
324
329
  # todo
325
- print(primitive_arg_types)
330
+ # print(primitive_arg_types)
326
331
 
327
332
  script.eval_list(kwargs, primitive_arg_types)
328
333
  kwargs = script.validate_variables(kwargs)
@@ -422,7 +427,9 @@ def get_operation_sidebar(instrument: str = ''):
422
427
  # edit_action_info = session.get("edit_action")
423
428
  html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
424
429
  defined_variables=deck_variables,
425
- local_variables=global_config.defined_variables)
430
+ local_variables=global_config.defined_variables,
431
+ block_variables=global_config.building_blocks,
432
+ )
426
433
  return jsonify({"html": html})
427
434
 
428
435
 
@@ -1,6 +1,6 @@
1
1
  {# Action form component #}
2
- <div class="accordion-item design-control" draggable="true">
3
- <h2 class="accordion-header">
2
+ <div class="accordion-item design-control">
3
+ <h2 class="accordion-header" >
4
4
  <button class="accordion-button collapsed draggable-action"
5
5
  type="button" data-bs-toggle="collapse"
6
6
  data-bs-target="#{{name}}" aria-expanded="false"
@@ -39,7 +39,29 @@
39
39
  </ul>
40
40
  </div>
41
41
  </div>
42
-
42
+ {% if block_variables %}
43
+ <div class="accordion-item design-control">
44
+ <h5 class="accordion-header">
45
+ <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#block" role="button" aria-expanded="false" aria-controls="collapseExample">
46
+ Methods
47
+ </button>
48
+ </h5>
49
+ <div class="accordion-collapse collapse show" id="block">
50
+ <ul class="list-group">
51
+ {% for category in block_variables %}
52
+ <button class="list-group-item list-group-item-action"
53
+ type="button"
54
+ name="device"
55
+ value="{{category}}"
56
+ data-get-url="{{ url_for('design.get_operation_sidebar', instrument=category) }}"
57
+ onclick="updateInstrumentPanel(this)">
58
+ {{ category|format_name }}
59
+ </button>
60
+ {% endfor%}
61
+ </ul>
62
+ </div>
63
+ </div>
64
+ {% endif %}
43
65
  {% if local_variables %}
44
66
  <div class="accordion-item design-control">
45
67
  <h5 class="accordion-header">