ivoryos 0.1.25__tar.gz → 1.0.1__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 (59) hide show
  1. {ivoryos-0.1.25/ivoryos.egg-info → ivoryos-1.0.1}/PKG-INFO +3 -3
  2. {ivoryos-0.1.25 → ivoryos-1.0.1}/README.md +2 -2
  3. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/control/control.py +11 -17
  4. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/database.py +42 -3
  5. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/design/design.py +123 -27
  6. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/design/templates/design/experiment_run.html +62 -123
  7. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/js/socket_handler.js +13 -8
  8. ivoryos-1.0.1/ivoryos/utils/bo_campaign.py +87 -0
  9. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/db_models.py +27 -7
  10. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/global_config.py +16 -7
  11. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/script_runner.py +55 -40
  12. ivoryos-1.0.1/ivoryos/utils/task_runner.py +81 -0
  13. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/utils.py +0 -68
  14. ivoryos-1.0.1/ivoryos/version.py +1 -0
  15. {ivoryos-0.1.25 → ivoryos-1.0.1/ivoryos.egg-info}/PKG-INFO +3 -3
  16. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos.egg-info/SOURCES.txt +2 -0
  17. ivoryos-0.1.25/ivoryos/version.py +0 -1
  18. {ivoryos-0.1.25 → ivoryos-1.0.1}/LICENSE +0 -0
  19. {ivoryos-0.1.25 → ivoryos-1.0.1}/MANIFEST.in +0 -0
  20. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/__init__.py +0 -0
  21. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/config.py +0 -0
  22. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/__init__.py +0 -0
  23. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/auth/__init__.py +0 -0
  24. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/auth/auth.py +0 -0
  25. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/auth/templates/auth/login.html +0 -0
  26. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
  27. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/control/__init__.py +0 -0
  28. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers.html +0 -0
  29. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
  30. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
  31. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/__init__.py +0 -0
  32. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
  33. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/experiment_step_view.html +0 -0
  34. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/step_card.html +0 -0
  35. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/workflow_run_database.html +0 -0
  36. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/design/__init__.py +0 -0
  37. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/design/templates/design/experiment_builder.html +0 -0
  38. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/main/__init__.py +0 -0
  39. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/main/main.py +0 -0
  40. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/main/templates/main/help.html +0 -0
  41. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/routes/main/templates/main/home.html +0 -0
  42. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/favicon.ico +0 -0
  43. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  44. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  45. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/js/overlay.js +0 -0
  46. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/js/sortable_card.js +0 -0
  47. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/js/sortable_design.js +0 -0
  48. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/logo.webp +0 -0
  49. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/static/style.css +0 -0
  50. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/templates/base.html +0 -0
  51. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/__init__.py +0 -0
  52. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/client_proxy.py +0 -0
  53. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/form.py +0 -0
  54. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos/utils/llm_agent.py +0 -0
  55. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos.egg-info/dependency_links.txt +0 -0
  56. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos.egg-info/requires.txt +0 -0
  57. {ivoryos-0.1.25 → ivoryos-1.0.1}/ivoryos.egg-info/top_level.txt +0 -0
  58. {ivoryos-0.1.25 → ivoryos-1.0.1}/setup.cfg +0 -0
  59. {ivoryos-0.1.25 → ivoryos-1.0.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 0.1.25
3
+ Version: 1.0.1
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
@@ -14,8 +14,8 @@ License-File: LICENSE
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
16
  [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
17
- [![Published](https://img.shields.io/badge/Nature_Communications-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
18
-
17
+ [![Published](https://img.shields.io/badge/Nature_Comm.-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
18
+ [![Discord](https://img.shields.io/discord/1313641159356059770?label=Discord&logo=discord&color=5865F2)](https://discord.gg/AX5P9EdGVX)
19
19
 
20
20
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
21
21
  # ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
@@ -2,8 +2,8 @@
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
4
  [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
5
- [![Published](https://img.shields.io/badge/Nature_Communications-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
6
-
5
+ [![Published](https://img.shields.io/badge/Nature_Comm.-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
6
+ [![Discord](https://img.shields.io/discord/1313641159356059770?label=Discord&logo=discord&color=5865F2)](https://discord.gg/AX5P9EdGVX)
7
7
 
8
8
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
9
9
  # ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
@@ -6,8 +6,10 @@ from flask_login import login_required
6
6
  from ivoryos.utils.global_config import GlobalConfig
7
7
  from ivoryos.utils import utils
8
8
  from ivoryos.utils.form import create_form_from_module, format_name
9
+ from ivoryos.utils.task_runner import TaskRunner
9
10
 
10
11
  global_config = GlobalConfig()
12
+ runner = TaskRunner()
11
13
 
12
14
  control = Blueprint('control', __name__, template_folder='templates/control')
13
15
 
@@ -142,7 +144,9 @@ def controllers(instrument: str):
142
144
  if form and form.validate_on_submit():
143
145
  try:
144
146
  kwargs.pop("hidden_name")
145
- output = function_executable(**kwargs)
147
+ output = runner.run_single_step(instrument, method_name, kwargs, wait=True,
148
+ current_app=current_app._get_current_object())
149
+ # output = function_executable(**kwargs)
146
150
  flash(f"\nRun Success! Output value: {output}.")
147
151
  except Exception as e:
148
152
  flash(e.__str__())
@@ -170,24 +174,14 @@ def backend_control(instrument: str=None):
170
174
  forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
171
175
 
172
176
  if request.method == 'POST':
173
- all_kwargs = request.form.copy()
174
- method_name = all_kwargs.pop("hidden_name", None)
175
- # if method_name is not None:
177
+ method_name = request.form.get("hidden_name", None)
176
178
  form = forms.get(method_name, None)
177
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
178
- function_executable = getattr(inst_object, method_name)
179
179
  if form:
180
- # print(kwargs)
181
- try:
182
- kwargs.pop("hidden_name")
183
- output = function_executable(**kwargs)
184
- json_output = jsonify(output)
185
- except Exception as e:
186
- json_output = jsonify(e.__str__())
187
- return json_output, 400
188
- else:
189
- return "instrument not exist", 400
190
- return json_output, 200
180
+ kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
181
+ wait = request.form.get("hidden_wait", "true") == "true"
182
+ output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
183
+ current_app=current_app._get_current_object())
184
+ return jsonify(output), 200
191
185
 
192
186
 
193
187
  @control.route("/backend_control", methods=['GET'])
@@ -29,6 +29,11 @@ def edit_workflow(workflow_name):
29
29
  off_line = current_app.config["OFF_LINE"]
30
30
  if off_line and pseudo_name and not script.deck == pseudo_name:
31
31
  flash(f"Choose the deck with name {script.deck}")
32
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
33
+ return jsonify({
34
+ "script": script.as_dict(),
35
+ "python_script": script.compile(),
36
+ })
32
37
  return redirect(url_for('design.experiment_builder'))
33
38
 
34
39
 
@@ -101,7 +106,7 @@ def finalize():
101
106
  return redirect(url_for('design.experiment_builder'))
102
107
 
103
108
 
104
- @database.route("/database/")
109
+ @database.route("/database/", strict_slashes=False)
105
110
  @database.route("/database/<deck_name>")
106
111
  @login_required
107
112
  def load_from_database(deck_name=None):
@@ -131,7 +136,15 @@ def load_from_database(deck_name=None):
131
136
  per_page = 10
132
137
 
133
138
  workflows = query.paginate(page=page, per_page=per_page, error_out=False)
134
- return render_template("experiment_database.html", workflows=workflows, deck_list=deck_list, deck_name=deck_name)
139
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
140
+ workflows = query.all()
141
+ workflow_names = [w.name for w in workflows]
142
+ return jsonify({
143
+ "workflows": workflow_names,
144
+ })
145
+ else:
146
+ # return HTML
147
+ return render_template("experiment_database.html", workflows=workflows, deck_list=deck_list, deck_name=deck_name)
135
148
 
136
149
 
137
150
  @database.route("/edit_run_name", methods=['POST'])
@@ -200,6 +213,12 @@ def list_workflows():
200
213
  per_page = 10
201
214
 
202
215
  workflows = query.paginate(page=page, per_page=per_page, error_out=False)
216
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
217
+ workflows = query.all()
218
+ workflow_data = {w.id:{"workflow_name":w.name, "start_time":w.start_time} for w in workflows}
219
+ return jsonify({
220
+ "workflow_data": workflow_data,
221
+ })
203
222
  return render_template('workflow_run_database.html', workflows=workflows)
204
223
 
205
224
 
@@ -208,20 +227,40 @@ def get_workflow_steps(workflow_id):
208
227
  workflow = WorkflowRun.query.get_or_404(workflow_id)
209
228
  steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
210
229
 
211
- # Organize steps by phase + repeat_index
230
+ # Use full objects for template rendering
212
231
  grouped = {
213
232
  "prep": [],
214
233
  "script": {},
215
234
  "cleanup": [],
216
235
  }
217
236
 
237
+ # Use dicts for JSON response
238
+ grouped_json = {
239
+ "prep": [],
240
+ "script": {},
241
+ "cleanup": [],
242
+ }
243
+
218
244
  for step in steps:
245
+ step_dict = step.as_dict()
246
+
219
247
  if step.phase == "prep":
220
248
  grouped["prep"].append(step)
249
+ grouped_json["prep"].append(step_dict)
250
+
221
251
  elif step.phase == "script":
222
252
  grouped["script"].setdefault(step.repeat_index, []).append(step)
253
+ grouped_json["script"].setdefault(step.repeat_index, []).append(step_dict)
254
+
223
255
  elif step.phase == "cleanup" or step.method_name == "stop":
224
256
  grouped["cleanup"].append(step)
257
+ grouped_json["cleanup"].append(step_dict)
258
+
259
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
260
+ return jsonify({
261
+ "workflow_info": workflow.as_dict(),
262
+ "steps": grouped_json,
263
+ })
225
264
 
226
265
  return render_template("experiment_step_view.html", workflow=workflow, grouped=grouped)
227
266
 
@@ -15,7 +15,7 @@ from ivoryos.utils.client_proxy import create_function, export_to_python
15
15
  from ivoryos.utils.global_config import GlobalConfig
16
16
  from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
17
17
  create_form_from_action, create_all_builtin_forms
18
- from ivoryos.utils.db_models import Script
18
+ from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
19
19
  from ivoryos.utils.script_runner import ScriptRunner
20
20
  # from ivoryos.utils.utils import load_workflows
21
21
 
@@ -25,32 +25,45 @@ design = Blueprint('design', __name__, template_folder='templates/design')
25
25
  global_config = GlobalConfig()
26
26
  runner = ScriptRunner()
27
27
 
28
-
29
- @socketio.on('abort_pending')
30
- def handle_abort_pending():
28
+ def abort_pending():
31
29
  runner.abort_pending()
32
30
  socketio.emit('log', {'message': "aborted pending iterations"})
33
31
 
34
-
35
- @socketio.on('abort_current')
36
- def handle_abort_current():
32
+ def abort_current():
37
33
  runner.stop_execution()
38
34
  socketio.emit('log', {'message': "stopped next task"})
39
35
 
40
-
41
- @socketio.on('pause')
42
- def handle_pause():
36
+ def pause():
43
37
  runner.retry = False
44
38
  msg = runner.toggle_pause()
45
39
  socketio.emit('log', {'message': msg})
40
+ return msg
46
41
 
47
- @socketio.on('retry')
48
- def handle_pause():
42
+ def retry():
49
43
  runner.retry = True
50
44
  msg = runner.toggle_pause()
51
45
  socketio.emit('log', {'message': msg})
52
46
 
53
47
 
48
+ # ---- Socket.IO Event Handlers ----
49
+
50
+ @socketio.on('abort_pending')
51
+ def handle_abort_pending():
52
+ abort_pending()
53
+
54
+ @socketio.on('abort_current')
55
+ def handle_abort_current():
56
+ abort_current()
57
+
58
+ @socketio.on('pause')
59
+ def handle_pause():
60
+ pause()
61
+
62
+ @socketio.on('retry')
63
+ def handle_retry():
64
+ retry()
65
+
66
+
54
67
  @socketio.on('connect')
55
68
  def handle_abort_action():
56
69
  # Fetch log messages from local file
@@ -312,11 +325,18 @@ def experiment_run():
312
325
  config_preview = []
313
326
  config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
314
327
  try:
315
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
316
- except ValueError as e:
328
+ # todo
329
+ exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
330
+ # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
331
+
332
+ except Exception as e:
317
333
  flash(e.__str__())
318
- return redirect(url_for("design.experiment_builder"))
319
- # print(exec_string)
334
+ # handle api request
335
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
336
+ return jsonify({"error": e.__str__()})
337
+ else:
338
+ return redirect(url_for("design.experiment_builder"))
339
+
320
340
  config_file = request.args.get("filename")
321
341
  config = []
322
342
  if config_file:
@@ -356,25 +376,41 @@ def experiment_run():
356
376
  flash(f"This script is not compatible with current deck, import {script.deck}")
357
377
  if request.method == "POST":
358
378
  bo_args = None
359
- if "bo" in request.form:
360
- bo_args = request.form.to_dict()
361
- # ax_client = utils.ax_initiation(bo_args)
362
- if "online-config" in request.form:
363
- config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
364
- repeat = request.form.get('repeat', None)
379
+ compiled = False
380
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
381
+ payload_json = request.get_json()
382
+ compiled = True
383
+ if "kwargs" in payload_json:
384
+ config = payload_json["kwargs"]
385
+ elif "parameters" in payload_json:
386
+ bo_args = payload_json
387
+ repeat = payload_json.pop("repeat", None)
388
+ else:
389
+ if "bo" in request.form:
390
+ bo_args = request.form.to_dict()
391
+ if "online-config" in request.form:
392
+ config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
393
+ repeat = request.form.get('repeat', None)
365
394
 
366
395
  try:
367
396
  datapath = current_app.config["DATA_FOLDER"]
368
397
  run_name = script.validate_function_name(run_name)
369
398
  runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
370
399
  logger=g.logger, socketio=g.socketio, repeat_count=repeat,
371
- output_path=datapath, current_app=current_app._get_current_object()
400
+ output_path=datapath, compiled=compiled,
401
+ current_app=current_app._get_current_object()
372
402
  )
373
403
  if utils.check_config_duplicate(config):
374
404
  flash(f"WARNING: Duplicate in config entries.")
375
405
  except Exception as e:
376
- flash(e)
377
- return render_template('experiment_run.html', script=script.script_dict, filename=filename,
406
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
407
+ return jsonify({"error": e.__str__()})
408
+ else:
409
+ flash(e)
410
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
411
+ return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
412
+ else:
413
+ return render_template('experiment_run.html', script=script.script_dict, filename=filename,
378
414
  dot_py=exec_string, line_collection=line_collection,
379
415
  return_list=return_list, config_list=config_list, config_file_list=config_file_list,
380
416
  config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
@@ -629,6 +665,66 @@ def duplicate_action(id: int):
629
665
  return redirect(back)
630
666
 
631
667
 
632
- @design.route("/backend/status", methods=["GET"])
668
+ # ---- HTTP API Endpoints ----
669
+
670
+ @design.route("/api/status", methods=["GET"])
633
671
  def runner_status():
634
- return jsonify(runner.get_status())
672
+ runner_busy = global_config.runner_lock.locked()
673
+ status = {"busy": runner_busy}
674
+ task_status = global_config.runner_status
675
+ current_step = {}
676
+ # print(task_status)
677
+ if task_status is not None:
678
+ task_type = task_status["type"]
679
+ task_id = task_status["id"]
680
+ if task_type == "task":
681
+ step = SingleStep.query.get(task_id)
682
+ current_step = step.as_dict()
683
+ if task_type == "workflow":
684
+ workflow = WorkflowRun.query.get(task_id)
685
+ if workflow is not None:
686
+ latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
687
+ if latest_step is not None:
688
+ current_step = latest_step.as_dict()
689
+ status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
690
+ status["current_task"] = current_step
691
+ return jsonify(status), 200
692
+
693
+
694
+
695
+ @design.route("/api/abort_pending", methods=["POST"])
696
+ def api_abort_pending():
697
+ abort_pending()
698
+ return jsonify({"status": "ok"}), 200
699
+
700
+ @design.route("/api/abort_current", methods=["POST"])
701
+ def api_abort_current():
702
+ abort_current()
703
+ return jsonify({"status": "ok"}), 200
704
+
705
+ @design.route("/api/pause", methods=["POST"])
706
+ def api_pause():
707
+ msg = pause()
708
+ return jsonify({"status": "ok", "pause_status": msg}), 200
709
+
710
+ @design.route("/api/retry", methods=["POST"])
711
+ def api_retry():
712
+ retry()
713
+ return jsonify({"status": "ok, retrying failed step"}), 200
714
+
715
+
716
+ @design.route("/api/get_script", methods=["GET", "POST"])
717
+ def get_script():
718
+ script = utils.get_script_file()
719
+ script.sort_actions()
720
+ script_collection = script.compile()
721
+ if request.method == "POST":
722
+ # create a brand-new script
723
+ deck = global_config.deck
724
+ deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
725
+ script = Script(author=session.get('user'), deck=deck_name)
726
+ script_collection = request.get_json()
727
+ script.python_script = script_collection
728
+ utils.post_script_file(script)
729
+ return jsonify({"status": "ok"}), 200
730
+ return jsonify(script_collection), 200
@@ -26,8 +26,8 @@
26
26
  </h2>
27
27
  <div id="runpanel" class="accordion-collapse collapse show">
28
28
  <div class="accordion-body">
29
- {% if script['script'] or script['prep'] or script['cleanup'] %}
30
- <div class="row">
29
+ <div class="row">
30
+ {% if script['script'] or script['prep'] or script['cleanup'] %}
31
31
  <div class="col-lg-6 col-sm-12" id="run-panel" style="{{ 'display: none;' if pause_status else '' }}">
32
32
  <ul class="nav nav-tabs" id="myTabs" role="tablist">
33
33
  <li class="nav-item" role="presentation">
@@ -127,129 +127,62 @@
127
127
  </form>
128
128
  {% endif %}
129
129
  </div>
130
-
131
- {# <div class="tab-pane fade " id="tab3" role="tabpanel" aria-labelledby="tab3-tab">#}
132
- {# <form role="form" method='POST' name="bo" action="{{ url_for('design.experiment_run')}}">#}
133
- {# <div class="form-group">#}
134
- {# <p><h5>Parameters:</h5><p>#}
135
- {# {% for config in config_list %}#}
136
- {# <div class="row g-3 align-items-center">#}
137
- {# <div class="col-lg-3 col-sm-6 ">#}
138
- {# {{config}}:#}
139
- {# </div>#}
140
- {# <div class="col-auto">#}
141
- {# <label class="col-form-label" for="{{config}}_type">Type</label>#}
142
- {# </div>#}
143
- {# <div class="col-auto">#}
144
- {# <select class="form-select" id="{{config}}_type" name="{{config}}_type">#}
145
- {# <option selected value="range">range</option>#}
146
- {# <option value="choice">choice</option>#}
147
- {# <option value="fixed">fixed</option>#}
148
- {# </select>#}
149
- {# </div>#}
150
- {# <div class="col-auto">#}
151
- {# <label class="" for="{{config}}_value">Values</label>#}
152
- {# </div>#}
153
- {# <div class="col-auto">#}
154
- {# <input type="text" class="form-control" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">#}
155
- {# </div>#}
156
- {# <div class="col-auto">#}
157
- {# <input type="text" class="form-control" id="{{config}}_value_max" style="display: none;" placeholder="1, 2, 3">#}
158
- {# </div>#}
159
- {# </div>#}
160
- {# {% endfor %}#}
161
- {# <p><h5>Objective:</h5><p>#}
162
- {# {% for objective in return_list %}#}
163
- {# <div class="row gy-2 gx-3 align-items-center input-group">#}
164
- {# <div class="col-3">#}
165
- {# {{objective}}:#}
166
- {# </div>#}
167
- {# <div class="col-auto">#}
168
- {# <label class="" for="{{objective}}_min">Minimize</label>#}
169
- {# </div>#}
170
- {# <div class="col-auto">#}
171
- {# <select class="form-select" id="{{objective}}_min" name="{{objective}}_min">#}
172
- {# <option selected>minimize</option>#}
173
- {# <option>maximize</option>#}
174
- {# <option>none</option>#}
175
- {# </select>#}
176
- {# </div>#}
177
- {# <div class="col-auto">#}
178
- {# <label class="" for="{{objective}}_threshold">Threshold</label>#}
179
- {# </div>#}
180
- {# <div class="col-auto">#}
181
- {# <input type="text" class="form-control" id="{{objective}}_threshold" name="{{objective}}_threshold" placeholder="None">#}
182
- {# </div>#}
183
- {# </div>#}
184
- {# {% endfor %}#}
185
- {# <p><h5>Budget:</h5></p>#}
186
- {# <div class="input-group mb-3">#}
187
- {# <label class="input-group-text" for="repeat">Max iteration </label>#}
188
- {# <input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">#}
189
- {# </div>#}
190
- {# {% if not no_deck_warning%}#}
191
- {# <div class="input-group mb-3">#}
192
- {# <button class="form-control" type="submit" name="bo">Run</button>#}
193
- {# </div>#}
194
- {# {% endif %}#}
195
- {# </div>#}
196
- {# </form>#}
197
- {# </div>#}
198
- <div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
199
- <form method="POST" name="bo" action="{{ url_for('design.experiment_run') }}">
200
- <div class="container py-2">
201
130
 
202
- <!-- Parameters -->
203
- <h6 class="fw-bold mt-2 mb-1">Parameters</h6>
204
- {% for config in config_list %}
205
- <div class="row align-items-center mb-2">
206
- <div class="col-3 col-form-label-sm">
207
- {{ config }}:
208
- </div>
209
- <div class="col-6">
210
- <select class="form-select form-select-sm" id="{{config}}_type" name="{{config}}_type">
211
- <option selected value="range">range</option>
212
- <option value="choice">choice</option>
213
- <option value="fixed">fixed</option>
214
- </select>
215
- </div>
216
- <div class="col-3">
217
- <input type="text" class="form-control form-control-sm" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">
218
- </div>
219
- </div>
220
- {% endfor %}
131
+ <div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
132
+ <form method="POST" name="bo" action="{{ url_for('design.experiment_run') }}">
133
+ <div class="container py-2">
221
134
 
222
- <!-- Objective -->
223
- <h6 class="fw-bold mt-3 mb-1">Objectives</h6>
224
- {% for objective in return_list %}
225
- <div class="row align-items-center mb-2">
226
- <div class="col-3 col-form-label-sm">
227
- {{ objective }}:
228
- </div>
229
- <div class="col-6">
230
- <select class="form-select form-select-sm" id="{{objective}}_min" name="{{objective}}_min">
231
- <option selected>minimize</option>
232
- <option>maximize</option>
233
- <option>none</option>
234
- </select>
235
- </div>
236
- </div>
237
- {% endfor %}
135
+ <!-- Parameters -->
136
+ <h6 class="fw-bold mt-2 mb-1">Parameters</h6>
137
+ {% for config in config_list %}
138
+ <div class="row align-items-center mb-2">
139
+ <div class="col-3 col-form-label-sm">
140
+ {{ config }}:
141
+ </div>
142
+ <div class="col-6">
143
+ <select class="form-select form-select-sm" id="{{config}}_type" name="{{config}}_type">
144
+ <option selected value="range">range</option>
145
+ <option value="choice">choice</option>
146
+ <option value="fixed">fixed</option>
147
+ </select>
148
+ </div>
149
+ <div class="col-3">
150
+ <input type="text" class="form-control form-control-sm" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">
151
+ </div>
152
+ </div>
153
+ {% endfor %}
238
154
 
239
- <h6 class="fw-bold mt-3 mb-1">Budget</h6>
155
+ <!-- Objective -->
156
+ <h6 class="fw-bold mt-3 mb-1">Objectives</h6>
157
+ {% for objective in return_list %}
158
+ <div class="row align-items-center mb-2">
159
+ <div class="col-3 col-form-label-sm">
160
+ {{ objective }}:
161
+ </div>
162
+ <div class="col-6">
163
+ <select class="form-select form-select-sm" id="{{objective}}_min" name="{{objective}}_min">
164
+ <option selected>minimize</option>
165
+ <option>maximize</option>
166
+ <option>none</option>
167
+ </select>
168
+ </div>
169
+ </div>
170
+ {% endfor %}
240
171
 
241
- <div class="input-group mb-3">
242
- <label class="input-group-text" for="repeat">Max iteration </label>
243
- <input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">
244
- </div>
245
- {% if not no_deck_warning%}
246
- <div class="input-group mb-3">
247
- <button class="form-control" type="submit" name="bo">Run</button>
248
- </div>
249
- {% endif %}
250
- </div>
251
- </form>
252
- </div>
172
+ <h6 class="fw-bold mt-3 mb-1">Budget</h6>
173
+
174
+ <div class="input-group mb-3">
175
+ <label class="input-group-text" for="repeat">Max iteration </label>
176
+ <input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">
177
+ </div>
178
+ {% if not no_deck_warning%}
179
+ <div class="input-group mb-3">
180
+ <button class="form-control" type="submit" name="bo">Run</button>
181
+ </div>
182
+ {% endif %}
183
+ </div>
184
+ </form>
185
+ </div>
253
186
 
254
187
 
255
188
  <div class="tab-pane fade {{ 'show active' if config_list and config_list|count<=5 else '' }}" id="tab4" role="tabpanel" aria-labelledby="tab4-tab">
@@ -278,6 +211,12 @@
278
211
  </div>
279
212
  </div>
280
213
  </div>
214
+ {% else %}
215
+ <div class="col-lg-6 col-sm-12" id="placeholder-panel">
216
+
217
+ </div>
218
+ {% endif %}
219
+
281
220
  <div class="col-lg-6 col-sm-12" id="code-panel" style="{{ '' if pause_status else 'display: none;'}}">
282
221
  <p>
283
222
  <h5>Progress:</h5>
@@ -299,7 +238,7 @@
299
238
  {% endif %}
300
239
  {% if "cleanup" in line_collection.keys() %}
301
240
  {% set stype = "cleanup" %}
302
- <h6>Experiment:</h6>
241
+ <h6>Cleanup:</h6>
303
242
  {% for code in line_collection["cleanup"] %}
304
243
  <pre style="margin: 0; padding: 0; line-height: 1;"><code class="python" id="{{ stype }}-{{ loop.index0 }}" >{{code}}</code></pre>
305
244
  {% endfor %}
@@ -337,7 +276,7 @@
337
276
  <div id="logging-panel"></div>
338
277
  </div>
339
278
  </div>
340
- {% endif %}
279
+
341
280
  </div>
342
281
  </div>
343
282
  </div>