ivoryos 0.1.19__tar.gz → 0.1.21__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 (54) hide show
  1. {ivoryos-0.1.19/ivoryos.egg-info → ivoryos-0.1.21}/PKG-INFO +6 -7
  2. {ivoryos-0.1.19 → ivoryos-0.1.21}/README.md +5 -6
  3. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/__init__.py +8 -11
  4. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/database/database.py +2 -0
  5. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/database/templates/database/experiment_database.html +3 -1
  6. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/design/design.py +61 -17
  7. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/design/templates/design/experiment_builder.html +84 -94
  8. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/design/templates/design/experiment_run.html +39 -3
  9. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/js/socket_handler.js +69 -16
  10. ivoryos-0.1.21/ivoryos/static/js/sortable_design.js +105 -0
  11. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/style.css +9 -0
  12. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/templates/base.html +3 -3
  13. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/db_models.py +44 -13
  14. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/form.py +50 -2
  15. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/global_config.py +10 -0
  16. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/script_runner.py +71 -43
  17. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/utils.py +1 -1
  18. ivoryos-0.1.21/ivoryos/version.py +1 -0
  19. {ivoryos-0.1.19 → ivoryos-0.1.21/ivoryos.egg-info}/PKG-INFO +6 -7
  20. ivoryos-0.1.19/ivoryos/static/js/sortable_design.js +0 -36
  21. ivoryos-0.1.19/ivoryos/version.py +0 -1
  22. {ivoryos-0.1.19 → ivoryos-0.1.21}/LICENSE +0 -0
  23. {ivoryos-0.1.19 → ivoryos-0.1.21}/MANIFEST.in +0 -0
  24. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/config.py +0 -0
  25. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/__init__.py +0 -0
  26. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/auth/__init__.py +0 -0
  27. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/auth/auth.py +0 -0
  28. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/auth/templates/auth/login.html +0 -0
  29. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
  30. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/control/__init__.py +0 -0
  31. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/control/control.py +0 -0
  32. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/control/templates/control/controllers.html +0 -0
  33. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
  34. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
  35. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/database/__init__.py +0 -0
  36. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/design/__init__.py +0 -0
  37. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/main/__init__.py +0 -0
  38. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/main/main.py +0 -0
  39. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/main/templates/main/help.html +0 -0
  40. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/routes/main/templates/main/home.html +0 -0
  41. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/favicon.ico +0 -0
  42. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  43. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  44. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/js/overlay.js +0 -0
  45. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/js/sortable_card.js +0 -0
  46. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/static/logo.webp +0 -0
  47. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/__init__.py +0 -0
  48. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos/utils/llm_agent.py +0 -0
  49. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos.egg-info/SOURCES.txt +0 -0
  50. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos.egg-info/dependency_links.txt +0 -0
  51. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos.egg-info/requires.txt +0 -0
  52. {ivoryos-0.1.19 → ivoryos-0.1.21}/ivoryos.egg-info/top_level.txt +0 -0
  53. {ivoryos-0.1.19 → ivoryos-0.1.21}/setup.cfg +0 -0
  54. {ivoryos-0.1.19 → ivoryos-0.1.21}/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.21
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
@@ -1,10 +1,8 @@
1
- import importlib
2
- import inspect
3
1
  import os
4
2
  import sys
5
3
  from typing import Union
6
4
 
7
- from flask import Flask, redirect, url_for, Blueprint, g
5
+ from flask import Flask, redirect, url_for, g
8
6
 
9
7
  from ivoryos.config import Config, get_config
10
8
  from ivoryos.routes.auth.auth import auth, login_manager
@@ -23,6 +21,11 @@ global_config = GlobalConfig()
23
21
 
24
22
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
25
23
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
24
+ app.register_blueprint(main, url_prefix=url_prefix)
25
+ app.register_blueprint(auth, url_prefix=url_prefix)
26
+ app.register_blueprint(control, url_prefix=url_prefix)
27
+ app.register_blueprint(design, url_prefix=url_prefix)
28
+ app.register_blueprint(database, url_prefix=url_prefix)
26
29
 
27
30
 
28
31
  def create_app(config_class=None):
@@ -86,14 +89,6 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
86
89
  """
87
90
  app = create_app(config_class=config or get_config()) # Create app instance using factory function
88
91
 
89
- app.register_blueprint(main, url_prefix=url_prefix)
90
- app.register_blueprint(auth, url_prefix=url_prefix)
91
- app.register_blueprint(control, url_prefix=url_prefix)
92
-
93
- if enable_design:
94
- app.register_blueprint(design, url_prefix=url_prefix)
95
- app.register_blueprint(database, url_prefix=url_prefix)
96
-
97
92
  plugins = load_plugins(app, socketio)
98
93
 
99
94
  def inject_nav_config():
@@ -156,3 +151,5 @@ def load_plugins(app, socketio):
156
151
  app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
157
152
 
158
153
  return plugin_names
154
+
155
+
@@ -176,10 +176,12 @@ def save_as():
176
176
  """
177
177
  if request.method == "POST":
178
178
  run_name = request.form.get("run_name")
179
+ register_workflow = request.form.get("register_workflow")
179
180
  exist_script = Script.query.get(run_name)
180
181
  if not exist_script:
181
182
  script = get_script_file()
182
183
  script.save_as(run_name)
184
+ script.registered = register_workflow == "on"
183
185
  script.author = session.get('user')
184
186
  post_script_file(script)
185
187
  publish()
@@ -26,10 +26,11 @@
26
26
  <tr>
27
27
  <th scope="col">Workflow name</th>
28
28
  <th scope="col">Deck </th>
29
- <th scope="col">Current status</th>
29
+ <th scope="col">Editing</th>
30
30
  <th scope="col">Time created</th>
31
31
  <th scope="col">Last modified</th>
32
32
  <th scope="col">Author</th>
33
+ <th scope="col">Registered</th>
33
34
  <th scope="col"></th>
34
35
  </tr>
35
36
  </thead>
@@ -42,6 +43,7 @@
42
43
  <td>{{ workflow.time_created }}</td>
43
44
  <td>{{ workflow.last_modified }}</td>
44
45
  <td>{{ workflow.author }}</td>
46
+ <td>{{ workflow.registered }}</td>
45
47
  <td>
46
48
  {#not workflow.status == "finalized" or#}
47
49
  {% if session['user'] == 'admin' or session['user'] == workflow.author %}
@@ -13,9 +13,10 @@ from werkzeug.utils import secure_filename
13
13
  from ivoryos.utils import utils
14
14
  from ivoryos.utils.global_config import GlobalConfig
15
15
  from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
16
- create_form_from_action
16
+ create_form_from_action, create_all_builtin_forms
17
17
  from ivoryos.utils.db_models import Script
18
18
  from ivoryos.utils.script_runner import ScriptRunner
19
+ # from ivoryos.utils.utils import load_workflows
19
20
 
20
21
  socketio = SocketIO()
21
22
  design = Blueprint('design', __name__, template_folder='templates/design')
@@ -92,6 +93,9 @@ def experiment_builder(instrument=None):
92
93
  """
93
94
  deck = global_config.deck
94
95
  script = utils.get_script_file()
96
+ # load_workflows(script)
97
+ # registered_workflows = global_config.registered_workflows
98
+
95
99
  if deck and script.deck is None:
96
100
  script.deck = os.path.splitext(os.path.basename(deck.__file__))[
97
101
  0] if deck.__name__ == "__main__" else deck.__name__
@@ -114,7 +118,10 @@ def experiment_builder(instrument=None):
114
118
 
115
119
  functions = {}
116
120
  if deck:
117
- deck_variables = global_config.deck_snapshot.keys()
121
+ deck_variables = list(global_config.deck_snapshot.keys())
122
+ # deck_variables.insert(0, "registered_workflows")
123
+ deck_variables.insert(0, "flow_control")
124
+
118
125
  else:
119
126
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
120
127
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
@@ -122,23 +129,31 @@ def experiment_builder(instrument=None):
122
129
  if edit_action_info:
123
130
  forms = create_form_from_action(edit_action_info, script=script)
124
131
  elif instrument:
125
- if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
126
- forms = create_builtin_form(instrument, script=script)
132
+ # if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
133
+ # forms = create_builtin_form(instrument, script=script)
134
+ if instrument == 'flow_control':
135
+ forms = create_all_builtin_forms(script=script)
136
+ # elif instrument == 'registered_workflows':
137
+ # functions = utils._inspect_class(registered_workflows)
138
+ # # forms = create_workflow_forms(script=script)
139
+ # forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
140
+ elif instrument in global_config.defined_variables.keys():
141
+ _object = global_config.defined_variables.get(instrument)
142
+ functions = utils._inspect_class(_object)
143
+ forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
127
144
  else:
128
145
  if deck:
129
146
  functions = global_config.deck_snapshot.get(instrument, {})
130
147
  elif pseudo_deck:
131
148
  functions = pseudo_deck.get(instrument, {})
132
- # print(function_metadata)
133
- # functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
134
149
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
135
150
  if request.method == 'POST' and "hidden_name" in request.form:
136
151
  # all_kwargs = request.form.copy()
137
152
  method_name = request.form.get("hidden_name", None)
138
153
  # if method_name is not None:
139
154
  form = forms.get(method_name)
155
+ insert_position = request.form.get("drop_target_id", None)
140
156
  kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
141
-
142
157
  if form and form.validate_on_submit():
143
158
  function_name = kwargs.pop("hidden_name")
144
159
  save_data = kwargs.pop('return', '')
@@ -151,30 +166,57 @@ def experiment_builder(instrument=None):
151
166
  "args": kwargs,
152
167
  "return": save_data,
153
168
  'arg_types': primitive_arg_types}
154
- script.add_action(action=action)
169
+ script.add_action(action=action, insert_position=insert_position)
155
170
  else:
156
171
  flash(form.errors)
157
172
 
158
173
  elif request.method == 'POST' and "builtin_name" in request.form:
159
- kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
160
- if forms.validate_on_submit():
174
+ function_name = request.form.get("builtin_name")
175
+ form = forms.get(function_name)
176
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
177
+ insert_position = request.form.get("drop_target_id", None)
178
+
179
+ if form.validate_on_submit():
161
180
  # print(kwargs)
162
181
  logic_type = kwargs.pop('builtin_name')
163
182
  if 'variable' in kwargs:
164
183
  try:
165
- script.add_variable(**kwargs)
184
+ script.add_variable(**kwargs, insert_position=insert_position)
166
185
  except ValueError:
167
186
  flash("Invalid variable type")
168
187
  else:
169
- script.add_logic_action(logic_type=logic_type, **kwargs)
188
+ script.add_logic_action(logic_type=logic_type, **kwargs, insert_position=insert_position)
170
189
  else:
171
- flash(forms.errors)
190
+ flash(form.errors)
191
+ elif request.method == 'POST' and "workflow_name" in request.form:
192
+ workflow_name = request.form.get("workflow_name")
193
+ form = forms.get(workflow_name)
194
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
195
+ insert_position = request.form.get("drop_target_id", None)
196
+
197
+ if form.validate_on_submit():
198
+ # workflow_name = kwargs.pop('workflow_name')
199
+ save_data = kwargs.pop('return', '')
200
+
201
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
202
+
203
+ script.eval_list(kwargs, primitive_arg_types)
204
+ kwargs = script.validate_variables(kwargs)
205
+ action = {"instrument": instrument, "action": workflow_name,
206
+ "args": kwargs,
207
+ "return": save_data,
208
+ 'arg_types': primitive_arg_types}
209
+ script.add_action(action=action, insert_position=insert_position)
210
+ script.add_workflow(**kwargs, insert_position=insert_position)
211
+ else:
212
+ flash(form.errors)
172
213
 
173
- # toggle autofill
214
+ # toggle autofill, autofill doesn't apply to control flow ops
174
215
  elif request.method == 'POST' and "autofill" in request.form:
175
216
  autofill = not autofill
176
- forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
177
217
  session['autofill'] = autofill
218
+ if not instrument == 'flow_control':
219
+ forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
178
220
 
179
221
  utils.post_script_file(script)
180
222
  design_buttons = create_action_button(script)
@@ -280,6 +322,7 @@ def experiment_run():
280
322
  try:
281
323
  for key, func_str in exec_string.items():
282
324
  exec(func_str)
325
+ line_collection = script.convert_to_lines(exec_string)
283
326
  except Exception:
284
327
  flash(f"Please check {key} syntax!!")
285
328
  return redirect(url_for("design.experiment_builder"))
@@ -323,11 +366,12 @@ def experiment_run():
323
366
  flash(f"WARNING: Duplicate in config entries.")
324
367
  except Exception as e:
325
368
  flash(e)
326
- return render_template('experiment_run.html', script=script.script_dict, filename=filename, dot_py=exec_string,
369
+ return render_template('experiment_run.html', script=script.script_dict, filename=filename,
370
+ dot_py=exec_string, line_collection=line_collection,
327
371
  return_list=return_list, config_list=config_list, config_file_list=config_file_list,
328
372
  config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
329
373
  no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
330
- history=deck_list)
374
+ history=deck_list, pause_status=runner.pause_status())
331
375
 
332
376
 
333
377
  @design.route("/toggle_script_type/<stype>")
@@ -2,13 +2,14 @@
2
2
  {% block title %}IvoryOS | Design{% endblock %}
3
3
 
4
4
  {% block body %}
5
- {# <div id="overlay" class="overlay">Generating design, please wait...</div>#}
5
+ {# overlay block for text-to-code gen #}
6
6
  <div id="overlay" class="overlay">
7
7
  <div>
8
8
  <h3 id="overlay-text">Generating design, please wait...</h3>
9
9
  <div class="spinner-border" role="status"></div>
10
10
  </div>
11
11
  </div>
12
+
12
13
  <div class="row">
13
14
  <div class="col-md-3 scroll-column" >
14
15
 
@@ -30,7 +31,6 @@
30
31
 
31
32
  {# edit action #}
32
33
  {% if session["edit_action"] %}
33
- {# {{ session["edit_action"] }}#}
34
34
  {% with action = session["edit_action"] %}
35
35
  <h5> {{ format_name(action['action']) }} </h5>
36
36
  <form role="form" method='POST' name="{{instrument}}" action="{{ url_for('design.edit_action', uuid=session["edit_action"]['uuid']) }}">
@@ -69,91 +69,71 @@
69
69
  <button class="btn btn-primary" type="submit" name="back" id="back" value="back">Back</button>
70
70
  </form>
71
71
  {% endwith %}
72
+
73
+
72
74
  {% elif instrument %}
73
75
  <div>
74
76
  <div class="d-flex justify-content-between align-items-center " style="margin-bottom: 1vh;margin-top: 1vh;">
75
-
76
77
  <a class="btn btn-primary" role="button" type="button" href="{{url_for('design.experiment_builder')}}"><i class="bi bi-arrow-return-left"></i></a>
77
-
78
78
  {{ format_name(instrument) }}
79
-
80
-
81
79
  </div>
82
80
 
83
- <!-- <div class="rounded flex-fill" style="height: 30px;background-color: aliceblue">-->
84
- <!-- <h6 style=" text-align: center; ">{{ format_name(instrument) }}</h6>-->
85
- <!-- </div>-->
86
- {% if instrument in ['if' , 'while' , 'variable' ,'wait', 'repeat'] %}
87
- {# form for builtin functions #}
88
- <form role="form" method='POST' name="{{instrument}}" action="{{url_for('design.experiment_builder',instrument=instrument)}}" >
89
- <div class="form-group">
90
- {{ forms.hidden_tag() }}
91
- {% for field in forms %}
92
- {% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
93
- <div class="input-group mb-3">
94
- <label class="input-group-text">{{ field.label.text }}</label>
95
- {{ field(class="form-control") }}
96
- <div class="form-text">{{ field.description }} </div>
97
- </div>
98
- {% endif %}
99
- {% endfor %}
100
- </div>
101
- <button type="submit" class="btn btn-dark">Add </button>
102
- </form>
103
- {% else %}
104
- {% if script.editing_type == "script" %}
105
- {# Auto Fill Toggle #}
106
- <div class="d-flex justify-content-between align-items-center " style="margin-bottom: 1vh;margin-top: 1vh;">
107
-
108
- <div></div>
109
- <form role="form" method='POST' name="autoFill" id="autoFill">
110
- <div class="form-check form-switch">
111
- <input type="hidden" id="autofill" name="autofill" value="temp_value">
112
- <input class="form-check-input" type="checkbox" id="autoFillCheck" name="autoFillCheck" onchange="document.getElementById('autoFill').submit();"
113
- value="temp_value"
114
- {{ "checked" if session["autofill"] else "" }}>
115
- <label class="form-check-label" for="autoFillCheck">Auto fill</label>
116
- </div>
117
- <button type="submit" class="btn btn-default" style="display: none;">Auto fill </button>
118
- </form>
119
- </div>
120
- {% endif %}
81
+ {% if script.editing_type == "script" %}
82
+ {# Auto Fill Toggle #}
83
+ <div class="d-flex justify-content-between align-items-center " style="margin-bottom: 1vh;margin-top: 1vh;">
84
+ <div></div>
85
+ <form role="form" method='POST' name="autoFill" id="autoFill">
86
+ <div class="form-check form-switch">
87
+ <input type="hidden" id="autofill" name="autofill" value="temp_value">
88
+ <input class="form-check-input" type="checkbox" id="autoFillCheck" name="autoFillCheck" onchange="document.getElementById('autoFill').submit();"
89
+ value="temp_value"
90
+ {{ "checked" if session["autofill"] else "" }}>
91
+ <label class="form-check-label" for="autoFillCheck">Auto fill</label>
92
+ </div>
93
+ <button type="submit" class="btn btn-default" style="display: none;">Auto fill </button>
94
+ </form>
95
+ </div>
96
+ {% endif %}
121
97
 
122
98
  {# according for instrument #}
123
99
  <div class="accordion accordion-flush" id="accordionActions" >
124
- {% if use_llm %}
125
- <div class="accordion-item text-to-code">
126
- <h2 class="accordion-header">
127
- <button class="accordion-button text-to-code" type="button" data-bs-toggle="collapse" data-bs-target="#text-to-code" aria-expanded="false" aria-controls="collapseExample">
128
- Text-to-Code
129
- </button>
130
- </h2>
131
- <div id="text-to-code" class="accordion-collapse collapse show" data-bs-parent="#accordionActions">
132
- <div class="accordion-body">
133
- <form role="form" method='POST' name="generate" id="generate" action="{{url_for('design.generate_code')}}">
134
- <input type="hidden" id="instrument" name="instrument" value="{{instrument}}">
135
- <textarea class="form-control" id="prompt" name="prompt" rows="6" aria-describedby="promptHelpBlock">{{ session['prompt'][instrument] if instrument in session['prompt'] else '' }}</textarea>
136
- <div id="promptHelpBlock" class="form-text">
137
- This will overwrite current design.
138
- </div>
139
- <!-- <button type="submit" class="btn btn-dark" id="clear" name="clear" onclick="submitForm('generate')">Clear</button>-->
140
- <button type="submit" class="btn btn-dark" id="gen" name="gen">Generate</button>
141
- </form>
142
- </div>
100
+ {% if use_llm and not instrument == "flow_control" %}
101
+ <div class="accordion-item text-to-code">
102
+ <h2 class="accordion-header">
103
+ <button class="accordion-button text-to-code" type="button" data-bs-toggle="collapse" data-bs-target="#text-to-code" aria-expanded="false" aria-controls="collapseExample">
104
+ Text-to-Code
105
+ </button>
106
+ </h2>
107
+ <div id="text-to-code" class="accordion-collapse collapse show" data-bs-parent="#accordionActions">
108
+ <div class="accordion-body">
109
+ <form role="form" method='POST' name="generate" id="generate" action="{{url_for('design.generate_code')}}">
110
+ <input type="hidden" id="instrument" name="instrument" value="{{instrument}}">
111
+ <textarea class="form-control" id="prompt" name="prompt" rows="6" aria-describedby="promptHelpBlock">{{ session['prompt'][instrument] if instrument in session['prompt'] else '' }}</textarea>
112
+ <div id="promptHelpBlock" class="form-text">
113
+ This will overwrite current design.
114
+ </div>
115
+ <!-- <button type="submit" class="btn btn-dark" id="clear" name="clear" onclick="submitForm('generate')">Clear</button>-->
116
+ <button type="submit" class="btn btn-dark" id="gen" name="gen">Generate</button>
117
+ </form>
143
118
  </div>
119
+ </div>
144
120
  </div>
145
121
  {% endif %}
122
+
146
123
  {% for name, form in forms.items() %}
147
- <div class="accordion-item design-control">
124
+ <div class="accordion-item design-control" draggable="true">
148
125
  <h2 class="accordion-header">
149
- <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#{{name}}" aria-expanded="false" aria-controls="collapseExample">
126
+ <button class="accordion-button collapsed draggable-action"
127
+ type="button" data-bs-toggle="collapse"
128
+ data-bs-target="#{{name}}" aria-expanded="false"
129
+ aria-controls="collapseExample"
130
+ data-action="{{ name }}">
150
131
  {{ format_name(name) }}
151
132
  </button>
152
133
  </h2>
153
-
154
134
  <div id="{{name}}" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
155
135
  <div class="accordion-body">
156
- <form role="form" method='POST' name="add" id="add">
136
+ <form role="form" method='POST' name="add" id="add-{{name}}">
157
137
  <div class="form-group">
158
138
  {{ form.hidden_tag() }}
159
139
  {% for field in form %}
@@ -171,18 +151,21 @@
171
151
  {% endif %}
172
152
  {% endfor %}
173
153
  </div>
174
- <button type="submit" class="btn btn-dark">Add </button>
175
- <i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top" title='{{ form.hidden_name.description or "Docstring is not available" }}' ></i>
154
+ <button type="submit" class="btn btn-dark">Add</button>
155
+ {% if 'hidden_name' in form %}
156
+ <i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top"
157
+ title='{{ form.hidden_name.description or "Docstring is not available" }}'>
158
+ </i>
159
+ {% else %}
160
+ <!-- handle info tooltip for flow control / workflows -->
161
+ {% endif %}
176
162
 
177
163
  </form>
178
-
179
-
180
164
  </div>
181
165
  </div>
182
166
  </div>
183
167
  {% endfor %}
184
168
  </div>
185
- {% endif %}
186
169
  </div>
187
170
 
188
171
 
@@ -190,29 +173,11 @@
190
173
  {% else %}
191
174
  <div style="margin-bottom: 4vh;"></div>
192
175
  <div class="accordion accordion-flush">
193
- <div class="accordion-item design-control">
194
- <h5 class="accordion-header">
195
- <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#advanced" role="button" aria-expanded="false" aria-controls="collapseExample">
196
- Builtin Operations:
197
- </button>
198
- </h5>
199
- <div class="accordion-collapse collapse show" id="advanced">
200
- <ul class="list-group">
201
- {% for instrument in ['if', 'while', 'variable', 'wait', 'repeat'] %}
202
- <form role="form" method='GET' name="device" action="{{url_for('design.experiment_builder',instrument=instrument)}}">
203
- <div class="form-group">
204
- <button class="list-group-item list-group-item-action" aria-current="true" type="submit">{{instrument}}</button>
205
- </div>
206
- </form>
207
- {% endfor %}
208
- </ul>
209
- </div>
210
- </div>
211
176
 
212
177
  <div class="accordion-item design-control">
213
178
  <h5 class="accordion-header">
214
179
  <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#deck" role="button" aria-expanded="false" aria-controls="collapseExample">
215
- Deck Actions:
180
+ Operations
216
181
  </button>
217
182
  </h5>
218
183
  <div class="accordion-collapse collapse show" id="deck">
@@ -232,7 +197,7 @@
232
197
  <div class="accordion-item design-control">
233
198
  <h5 class="accordion-header">
234
199
  <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#local" role="button" aria-expanded="false" aria-controls="collapseExample">
235
- Local Actions:
200
+ Local Operations
236
201
  </button>
237
202
  </h5>
238
203
  <div class="accordion-collapse collapse show" id="local">
@@ -279,7 +244,7 @@
279
244
  </ul>
280
245
  </div>
281
246
 
282
- <div class="canvas">
247
+ <div class="canvas" droppable="true">
283
248
  <div class="collapse" id="info">
284
249
  <table class="table script-table">
285
250
  <tbody>
@@ -363,6 +328,10 @@
363
328
  <label class="input-group-text" for="run_name">Run Name</label>
364
329
  <input class="form-control" type="text" name="run_name" id="run_name" placeholder="{{script['name']}}" required="required">
365
330
  </div>
331
+ <div class="form-check form-switch">
332
+ <input class="form-check-input" type="checkbox" name="register_workflow" id="register_workflow">
333
+ <label class="input-group-label" for="register_workflow">Register this workflow</label>
334
+ </div>
366
335
  </div>
367
336
  <div class="modal-footer">
368
337
  <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button>
@@ -415,8 +384,29 @@
415
384
  </div>
416
385
  </div>
417
386
  </div>
387
+ <!-- Bootstrap Modal -->
388
+ <div class="modal fade" id="dropModal" tabindex="-1" aria-labelledby="dropModalLabel" aria-hidden="true">
389
+ <div class="modal-dialog">
390
+ <div class="modal-content">
391
+ <div class="modal-header">
392
+ <h5 class="modal-title" id="dropModalLabel">Configure Action</h5>
393
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
394
+ </div>
395
+ <div class="modal-body">
396
+ <p>Drop Position ID: <strong id="modalDropTarget"></strong></p>
397
+
398
+ <!-- Form will be dynamically inserted here -->
399
+ <div id="modalFormFields"></div>
400
+ </div>
401
+ <div class="modal-footer">
402
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+
418
408
 
419
- {% if instrument and not instrument in ['if' , 'while' , 'variable' ,'wait', 'repeat'] and use_llm %}
409
+ {% if instrument and use_llm %}
420
410
  <script>
421
411
  const buttonIds = {{ ['generate'] | tojson }};
422
412
  </script>