ivoryos 0.1.19__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.

Files changed (53) hide show
  1. {ivoryos-0.1.19/ivoryos.egg-info → ivoryos-0.1.20}/PKG-INFO +6 -7
  2. {ivoryos-0.1.19 → ivoryos-0.1.20}/README.md +5 -6
  3. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/design/design.py +3 -1
  4. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_run.html +29 -2
  5. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/js/socket_handler.js +10 -0
  6. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/db_models.py +21 -3
  7. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/script_runner.py +22 -17
  8. ivoryos-0.1.20/ivoryos/version.py +1 -0
  9. {ivoryos-0.1.19 → ivoryos-0.1.20/ivoryos.egg-info}/PKG-INFO +6 -7
  10. ivoryos-0.1.19/ivoryos/version.py +0 -1
  11. {ivoryos-0.1.19 → ivoryos-0.1.20}/LICENSE +0 -0
  12. {ivoryos-0.1.19 → ivoryos-0.1.20}/MANIFEST.in +0 -0
  13. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/__init__.py +0 -0
  14. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/config.py +0 -0
  15. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/__init__.py +0 -0
  16. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/auth/__init__.py +0 -0
  17. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/auth/auth.py +0 -0
  18. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/auth/templates/auth/login.html +0 -0
  19. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
  20. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/control/__init__.py +0 -0
  21. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/control/control.py +0 -0
  22. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers.html +0 -0
  23. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
  24. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
  25. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/database/__init__.py +0 -0
  26. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/database/database.py +0 -0
  27. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
  28. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/design/__init__.py +0 -0
  29. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/design/templates/design/experiment_builder.html +0 -0
  30. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/main/__init__.py +0 -0
  31. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/main/main.py +0 -0
  32. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/main/templates/main/help.html +0 -0
  33. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/routes/main/templates/main/home.html +0 -0
  34. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/favicon.ico +0 -0
  35. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  36. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  37. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/js/overlay.js +0 -0
  38. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/js/sortable_card.js +0 -0
  39. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/js/sortable_design.js +0 -0
  40. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/logo.webp +0 -0
  41. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/static/style.css +0 -0
  42. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/templates/base.html +0 -0
  43. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/__init__.py +0 -0
  44. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/form.py +0 -0
  45. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/global_config.py +0 -0
  46. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/llm_agent.py +0 -0
  47. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos/utils/utils.py +0 -0
  48. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos.egg-info/SOURCES.txt +0 -0
  49. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos.egg-info/dependency_links.txt +0 -0
  50. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos.egg-info/requires.txt +0 -0
  51. {ivoryos-0.1.19 → ivoryos-0.1.20}/ivoryos.egg-info/top_level.txt +0 -0
  52. {ivoryos-0.1.19 → ivoryos-0.1.20}/setup.cfg +0 -0
  53. {ivoryos-0.1.19 → 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.19
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
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
14
14
  [![PyPI version](https://img.shields.io/pypi/v/ivoryos)](https://pypi.org/project/ivoryos/)
15
15
  ![License](https://img.shields.io/pypi/l/ivoryos)
16
+ [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
17
+ [![Research Square](https://img.shields.io/badge/Preprint-blue)](https://www.researchsquare.com/article/rs-5307798/v1)
18
+
16
19
 
17
20
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
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,15 +156,10 @@ 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
- ### Demo video
156
- Intro + Tutorial + Demo with PurPOSE platform
157
- https://youtu.be/dFfJv9I2-1g
158
-
159
-
160
159
  ## Roadmap
161
160
 
162
161
  - [x] Allow plugin pages ✅
163
- - [ ] pause, resume, abort current and pending workflows
162
+ - [x] pause, resume, abort current and pending workflows
164
163
  - [ ] snapshot version control
165
164
  - [ ] dropdown input
166
165
  - [ ] show line number option
@@ -1,6 +1,9 @@
1
1
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
2
2
  [![PyPI version](https://img.shields.io/pypi/v/ivoryos)](https://pypi.org/project/ivoryos/)
3
3
  ![License](https://img.shields.io/pypi/l/ivoryos)
4
+ [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
5
+ [![Research Square](https://img.shields.io/badge/Preprint-blue)](https://www.researchsquare.com/article/rs-5307798/v1)
6
+
4
7
 
5
8
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
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,15 +144,10 @@ 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
- ### Demo video
144
- Intro + Tutorial + Demo with PurPOSE platform
145
- https://youtu.be/dFfJv9I2-1g
146
-
147
-
148
147
  ## Roadmap
149
148
 
150
149
  - [x] Allow plugin pages ✅
151
- - [ ] pause, resume, abort current and pending workflows
150
+ - [x] pause, resume, abort current and pending workflows
152
151
  - [ ] snapshot version control
153
152
  - [ ] dropdown input
154
153
  - [ ] show line number option
@@ -280,6 +280,7 @@ def experiment_run():
280
280
  try:
281
281
  for key, func_str in exec_string.items():
282
282
  exec(func_str)
283
+ line_collection = script.convert_to_lines(exec_string)
283
284
  except Exception:
284
285
  flash(f"Please check {key} syntax!!")
285
286
  return redirect(url_for("design.experiment_builder"))
@@ -323,7 +324,8 @@ def experiment_run():
323
324
  flash(f"WARNING: Duplicate in config entries.")
324
325
  except Exception as e:
325
326
  flash(e)
326
- return render_template('experiment_run.html', script=script.script_dict, filename=filename, dot_py=exec_string,
327
+ return render_template('experiment_run.html', script=script.script_dict, filename=filename,
328
+ dot_py=exec_string, line_collection=line_collection,
327
329
  return_list=return_list, config_list=config_list, config_file_list=config_file_list,
328
330
  config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
329
331
  no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
@@ -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,6 +217,16 @@
217
217
  </div>
218
218
  </div>
219
219
  </div>
220
+ <div class="col-lg-6 col-sm-12" id="code-panel" style="display: none;">
221
+ <p>
222
+ <h5>Progress:</h5>
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 %}
228
+ </p>
229
+ </div>
220
230
  <div class="col-lg-6 col-sm-12 logging-panel">
221
231
  <p>
222
232
  <div class="p d-flex justify-content-between align-items-center">
@@ -237,7 +247,6 @@
237
247
  <small><strong>Note:</strong> The current step cannot be paused or stopped until it completes. </small>
238
248
  </div>
239
249
 
240
- </p>
241
250
  <div class="progress" role="progressbar" aria-label="Animated striped example" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100">
242
251
  <div id="progress-bar-inner" class="progress-bar progress-bar-striped progress-bar-animated"></div>
243
252
  </div>
@@ -338,4 +347,22 @@
338
347
  }
339
348
  });
340
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
+
341
368
  {% endblock %}
@@ -10,11 +10,21 @@ document.addEventListener("DOMContentLoaded", function() {
10
10
  var progressBar = document.getElementById('progress-bar-inner');
11
11
  progressBar.style.width = progress + '%';
12
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";
13
16
 
17
+ // Optional: Scroll to the code panel
18
+ document.getElementById("code-panel").scrollIntoView({ behavior: "smooth" });
19
+ }
14
20
  if (progress === 100) {
15
21
  // Remove animation and set green color when 100% is reached
16
22
  progressBar.classList.remove('progress-bar-animated');
17
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
18
28
  }
19
29
  });
20
30
  socket.on('log', function(data) {
@@ -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.
@@ -401,8 +418,9 @@ class Script(db.Model):
401
418
  exec_str_collection = {}
402
419
 
403
420
  for i in self.stypes:
404
- func_str = self._generate_function_header(run_name, i) + self._generate_function_body(i)
405
- exec_str_collection[i] = func_str
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
406
424
  if script_path:
407
425
  self._write_to_file(script_path, run_name, exec_str_collection)
408
426
 
@@ -434,7 +452,7 @@ class Script(db.Model):
434
452
  function_header += ", ".join(configure)
435
453
 
436
454
  function_header += "):"
437
- function_header += self.indent(1) + f"global {run_name}_{stype}"
455
+ # function_header += self.indent(1) + f"global {run_name}_{stype}"
438
456
  return function_header
439
457
 
440
458
  def _generate_function_body(self, stype):
@@ -35,10 +35,12 @@ class ScriptRunner:
35
35
  return "Resumed"
36
36
 
37
37
  def reset_stop_event(self):
38
+ """Resets the stop event"""
38
39
  self.stop_pending_event.clear()
39
40
  self.stop_current_event.clear()
40
41
 
41
42
  def abort_pending(self):
43
+ """Abort the pending iteration after the current is finished"""
42
44
  self.stop_pending_event.set()
43
45
  # print("Stop pending tasks")
44
46
 
@@ -66,7 +68,7 @@ class ScriptRunner:
66
68
  thread.start()
67
69
  return thread
68
70
 
69
- def execute_function_line_by_line(self, script, section_name, logger, **kwargs):
71
+ def execute_function_line_by_line(self, lines:list, section_name, logger, socketio, **kwargs):
70
72
  """
71
73
  Executes a function defined in a string line by line.
72
74
 
@@ -79,11 +81,7 @@ class ScriptRunner:
79
81
  deck = global_config.deck
80
82
  # func_str = script.compile()
81
83
  # Parse function body from string
82
- module = ast.parse(script)
83
- func_def = next(node for node in module.body if isinstance(node, ast.FunctionDef))
84
84
 
85
- # Extract function body as source lines
86
- lines = [ast.unparse(node) for node in func_def.body if not isinstance(node, ast.Return)]
87
85
  # Prepare execution environment
88
86
  exec_globals = {"deck": deck} # Add required global objects
89
87
  exec_locals = {} # Local execution scope
@@ -92,35 +90,41 @@ class ScriptRunner:
92
90
  exec_locals.update(kwargs)
93
91
 
94
92
  # Execute each line dynamically
95
- for line in lines:
93
+ for index, line in enumerate(lines):
96
94
  if self.stop_current_event.is_set():
97
95
  logger.info(f'Stopping execution during {section_name}')
98
96
  break
99
97
  logger.info(f"Executing: {line}") # Debugging output
98
+ socketio.emit('execution', {'section': f"{section_name}-{index}"})
99
+ # self._emit_progress(socketio, 100)
100
100
  exec(line, exec_globals, exec_locals)
101
101
  self.pause_event.wait()
102
102
 
103
103
  return exec_locals # Return the 'results' variable
104
104
 
105
- def _run_with_stop_check(self, script: Script, repeat_count, run_name, logger, socketio, config, bo_args,
105
+ def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
106
106
  output_path):
107
107
  time.sleep(1)
108
108
  func_str = script.compile()
109
+ func_str_lines = script.convert_to_lines(func_str)
109
110
  self._emit_progress(socketio, 1)
110
111
  try:
111
112
  # Run "prep" section once
112
113
  script_dict = script.script_dict
113
- self._run_actions(script_dict.get("prep", []), func_str.get("prep", ''), section_name="prep", logger=logger)
114
+ self._run_actions(script_dict.get("prep", []), func_str_lines.get("prep", []), section_name="prep", logger=logger, socketio=socketio)
114
115
  output_list = []
115
116
  _, arg_type = script.config("script")
116
117
  _, return_list = script.config_return()
117
118
  # Run "script" section multiple times
118
119
  if repeat_count:
119
- self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, func_str.get("script", ''), run_name, return_list, logger, socketio)
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)
120
122
  elif config:
121
- self._run_config_section(config, arg_type, output_list, func_str.get("script", ''), run_name, logger, socketio)
123
+ self._run_config_section(config, arg_type, output_list, func_str_lines.get("script", []), run_name, logger,
124
+ socketio)
122
125
  # Run "cleanup" section once
123
- self._run_actions(script_dict.get("cleanup", []), func_str.get("cleanup", ''), section_name="cleanup", logger=logger)
126
+ self._run_actions(script_dict.get("cleanup", []), func_str.get("cleanup", []), section_name="cleanup",
127
+ logger=logger, socketio=socketio)
124
128
  # Save results if necessary
125
129
  if output_list:
126
130
  self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
@@ -131,12 +135,12 @@ class ScriptRunner:
131
135
  self.is_running = False # Reset the running flag when done
132
136
  self._emit_progress(socketio, 100)
133
137
 
134
- def _run_actions(self, actions, func_str, section_name="", logger=None):
138
+ def _run_actions(self, actions, func_str, section_name="", logger=None, socketio=None):
135
139
  logger.info(f'Executing {section_name} steps') if actions else logger.info(f'No {section_name} steps')
136
140
  if self.stop_pending_event.is_set():
137
141
  logger.info(f"Stopping execution during {section_name} section.")
138
142
  return
139
- self.execute_function_line_by_line(func_str, section_name, logger)
143
+ self.execute_function_line_by_line(func_str, section_name, logger, socketio)
140
144
 
141
145
  def _run_config_section(self, config, arg_type, output_list, func_str, run_name, logger, socketio):
142
146
  compiled = True
@@ -158,12 +162,13 @@ class ScriptRunner:
158
162
  self._emit_progress(socketio, progress)
159
163
  # fname = f"{run_name}_script"
160
164
  # function = self.globals_dict[fname]
161
- output = self.execute_function_line_by_line(func_str, "script", logger, **kwargs)
165
+ output = self.execute_function_line_by_line(func_str, "script", logger, socketio, **kwargs)
162
166
  if output:
163
167
  # kwargs.update(output)
164
168
  output_list.append(output)
165
169
 
166
- def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, func_str, run_name, return_list, logger, socketio):
170
+ def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, func_str, run_name, return_list,
171
+ logger, socketio):
167
172
  if bo_args:
168
173
  logger.info('Initializing optimizer...')
169
174
  ax_client = utils.ax_initiation(bo_args, arg_types)
@@ -180,7 +185,7 @@ class ScriptRunner:
180
185
  logger.info(f'Output value: {parameters}')
181
186
  # fname = f"{run_name}_script"
182
187
  # function = self.globals_dict[fname]
183
- output = self.execute_function_line_by_line(func_str, "script", logger, **parameters)
188
+ output = self.execute_function_line_by_line(func_str, "script", logger, socketio, **parameters)
184
189
 
185
190
  _output = {key: value for key, value in output.items() if key in return_list}
186
191
  ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
@@ -191,7 +196,7 @@ class ScriptRunner:
191
196
  else:
192
197
  # fname = f"{run_name}_script"
193
198
  # function = self.globals_dict[fname]
194
- output = self.execute_function_line_by_line(func_str, "script", logger)
199
+ output = self.execute_function_line_by_line(func_str, "script", logger, socketio)
195
200
 
196
201
  if output:
197
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.19
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
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
14
14
  [![PyPI version](https://img.shields.io/pypi/v/ivoryos)](https://pypi.org/project/ivoryos/)
15
15
  ![License](https://img.shields.io/pypi/l/ivoryos)
16
+ [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
17
+ [![Research Square](https://img.shields.io/badge/Preprint-blue)](https://www.researchsquare.com/article/rs-5307798/v1)
18
+
16
19
 
17
20
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
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,15 +156,10 @@ 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
- ### Demo video
156
- Intro + Tutorial + Demo with PurPOSE platform
157
- https://youtu.be/dFfJv9I2-1g
158
-
159
-
160
159
  ## Roadmap
161
160
 
162
161
  - [x] Allow plugin pages ✅
163
- - [ ] pause, resume, abort current and pending workflows
162
+ - [x] pause, resume, abort current and pending workflows
164
163
  - [ ] snapshot version control
165
164
  - [ ] dropdown input
166
165
  - [ ] show line number option
@@ -1 +0,0 @@
1
- __version__ = "0.1.19"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes