ivoryos 1.1.0__py3-none-any.whl → 1.2.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.

Files changed (58) hide show
  1. ivoryos/__init__.py +12 -5
  2. ivoryos/routes/api/api.py +5 -58
  3. ivoryos/routes/control/control.py +46 -43
  4. ivoryos/routes/control/control_file.py +4 -4
  5. ivoryos/routes/control/control_new_device.py +10 -10
  6. ivoryos/routes/control/templates/controllers.html +38 -9
  7. ivoryos/routes/control/templates/controllers_new.html +1 -1
  8. ivoryos/routes/data/data.py +81 -60
  9. ivoryos/routes/data/templates/components/step_card.html +9 -3
  10. ivoryos/routes/data/templates/workflow_database.html +10 -4
  11. ivoryos/routes/design/design.py +306 -243
  12. ivoryos/routes/design/design_file.py +42 -31
  13. ivoryos/routes/design/design_step.py +132 -30
  14. ivoryos/routes/design/templates/components/action_form.html +4 -3
  15. ivoryos/routes/design/templates/components/{instrument_panel.html → actions_panel.html} +7 -5
  16. ivoryos/routes/design/templates/components/autofill_toggle.html +8 -12
  17. ivoryos/routes/design/templates/components/canvas.html +5 -14
  18. ivoryos/routes/design/templates/components/canvas_footer.html +5 -1
  19. ivoryos/routes/design/templates/components/canvas_header.html +36 -15
  20. ivoryos/routes/design/templates/components/canvas_main.html +34 -0
  21. ivoryos/routes/design/templates/components/deck_selector.html +8 -10
  22. ivoryos/routes/design/templates/components/edit_action_form.html +16 -7
  23. ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
  24. ivoryos/routes/design/templates/components/modals/drop_modal.html +3 -5
  25. ivoryos/routes/design/templates/components/modals/new_script_modal.html +1 -2
  26. ivoryos/routes/design/templates/components/modals/rename_modal.html +5 -5
  27. ivoryos/routes/design/templates/components/modals/saveas_modal.html +2 -2
  28. ivoryos/routes/design/templates/components/python_code_overlay.html +26 -4
  29. ivoryos/routes/design/templates/components/sidebar.html +12 -13
  30. ivoryos/routes/design/templates/experiment_builder.html +20 -20
  31. ivoryos/routes/execute/execute.py +157 -13
  32. ivoryos/routes/execute/execute_file.py +38 -4
  33. ivoryos/routes/execute/templates/components/tab_bayesian.html +365 -114
  34. ivoryos/routes/execute/templates/components/tab_configuration.html +1 -1
  35. ivoryos/routes/library/library.py +70 -115
  36. ivoryos/routes/library/templates/library.html +27 -19
  37. ivoryos/static/js/action_handlers.js +213 -0
  38. ivoryos/static/js/db_delete.js +23 -0
  39. ivoryos/static/js/script_metadata.js +39 -0
  40. ivoryos/static/js/sortable_design.js +89 -56
  41. ivoryos/static/js/ui_state.js +113 -0
  42. ivoryos/utils/bo_campaign.py +137 -1
  43. ivoryos/utils/db_models.py +14 -5
  44. ivoryos/utils/form.py +4 -9
  45. ivoryos/utils/global_config.py +13 -1
  46. ivoryos/utils/script_runner.py +24 -5
  47. ivoryos/utils/serilize.py +203 -0
  48. ivoryos/utils/task_runner.py +4 -1
  49. ivoryos/version.py +1 -1
  50. {ivoryos-1.1.0.dist-info → ivoryos-1.2.0.dist-info}/METADATA +1 -1
  51. {ivoryos-1.1.0.dist-info → ivoryos-1.2.0.dist-info}/RECORD +54 -51
  52. ivoryos/routes/design/templates/components/action_list.html +0 -15
  53. ivoryos/routes/design/templates/components/operations_panel.html +0 -43
  54. ivoryos/routes/design/templates/components/script_info.html +0 -31
  55. ivoryos/routes/design/templates/components/scripts.html +0 -50
  56. {ivoryos-1.1.0.dist-info → ivoryos-1.2.0.dist-info}/LICENSE +0 -0
  57. {ivoryos-1.1.0.dist-info → ivoryos-1.2.0.dist-info}/WHEEL +0 -0
  58. {ivoryos-1.1.0.dist-info → ivoryos-1.2.0.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,19 @@
1
1
  import os
2
2
 
3
- from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, \
4
- current_app
3
+ from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, current_app
5
4
  from flask_login import login_required
6
5
 
7
6
  from ivoryos.routes.library.library import publish
8
7
  from ivoryos.utils import utils
9
8
  from ivoryos.utils.global_config import GlobalConfig
10
- from ivoryos.utils.form import create_action_button, format_name, create_form_from_pseudo, \
11
- create_form_from_action, create_all_builtin_forms
9
+ from ivoryos.utils.form import create_action_button, create_form_from_pseudo, create_all_builtin_forms
12
10
  from ivoryos.utils.db_models import Script
13
11
  from ivoryos.utils.py_to_json import convert_to_cards
14
- from ivoryos.utils.script_runner import ScriptRunner
15
12
 
16
13
  # Import the new modular components
17
14
  from ivoryos.routes.design.design_file import files
18
15
  from ivoryos.routes.design.design_step import steps
19
16
 
20
- # from ...utils.py_to_json import convert_to_cards
21
17
 
22
18
  design = Blueprint('design', __name__, template_folder='templates')
23
19
 
@@ -29,43 +25,38 @@ global_config = GlobalConfig()
29
25
 
30
26
  # ---- Main Design Routes ----
31
27
 
32
- @design.route("/script/", methods=['GET', 'POST'])
33
- @design.route("/script/<instrument>/", methods=['GET', 'POST'])
28
+
29
+ def _create_forms(instrument, script, autofill, pseudo_deck = None):
30
+ deck = global_config.deck
31
+ functions = {}
32
+ if instrument == 'flow_control':
33
+ forms = create_all_builtin_forms(script=script)
34
+ elif instrument in global_config.defined_variables.keys():
35
+ _object = global_config.defined_variables.get(instrument)
36
+ functions = utils._inspect_class(_object)
37
+ forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
38
+ else:
39
+ if deck:
40
+ functions = global_config.deck_snapshot.get(instrument, {})
41
+ elif pseudo_deck:
42
+ functions = pseudo_deck.get(instrument, {})
43
+ forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
44
+ return functions, forms
45
+
46
+ @design.route("/draft")
34
47
  @login_required
35
- def experiment_builder(instrument=None):
48
+ def experiment_builder():
36
49
  """
37
50
  .. :quickref: Workflow Design; Build experiment workflow
38
51
 
39
52
  **Experiment Builder**
40
53
 
41
- This route allows users to build and edit experiment workflows. Users can interact with available instruments,
42
- define variables, and manage experiment scripts.
43
-
44
- .. http:get:: /design/script
54
+ .. http:get:: /draft
45
55
 
46
- Load the experiment builder interface.
56
+ Load the experiment builder page where users can design their workflow by adding actions, instruments, and logic.
47
57
 
48
- :param instrument: The specific instrument for which to load functions and forms.
49
- :type instrument: str
50
58
  :status 200: Experiment builder loaded successfully.
51
59
 
52
- .. http:post:: /design/script
53
-
54
- Submit form data to add or modify actions in the experiment script.
55
-
56
- **Adding action to canvas**
57
-
58
- :form return: (optional) The name of the function or method to add to the script.
59
- :form dynamic: depend on the selected instrument and its metadata.
60
-
61
- :status 200: Action added or modified successfully.
62
- :status 400: Validation errors in submitted form data.
63
- :status 302: Toggles autofill or redirects to refresh the page.
64
-
65
- **Toggle auto parameter name fill**:
66
-
67
- :status 200: autofill toggled successfully
68
-
69
60
  """
70
61
  deck = global_config.deck
71
62
  script = utils.get_script_file()
@@ -77,266 +68,201 @@ def experiment_builder(instrument=None):
77
68
  pseudo_deck_name = session.get('pseudo_deck', '')
78
69
  pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
79
70
  off_line = current_app.config["OFF_LINE"]
80
- enable_llm = current_app.config["ENABLE_LLM"]
81
- autofill = session.get('autofill')
82
71
 
83
- # autofill is not allowed for prep and cleanup
84
- autofill = autofill if script.editing_type == "script" else False
85
- forms = None
86
72
  pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
87
73
  if off_line and pseudo_deck is None:
88
74
  flash("Choose available deck below.")
89
75
 
90
76
  deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
91
77
 
92
- functions = {}
93
78
  if deck:
94
79
  deck_variables = list(global_config.deck_snapshot.keys())
95
- deck_variables.insert(0, "flow_control")
80
+ # deck_variables.insert(0, "flow_control")
96
81
  else:
97
82
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
98
83
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
99
-
100
- edit_action_info = session.get("edit_action")
101
- if edit_action_info:
102
- forms = create_form_from_action(edit_action_info, script=script)
103
- elif instrument:
104
- if instrument == 'flow_control':
105
- forms = create_all_builtin_forms(script=script)
106
- elif instrument in global_config.defined_variables.keys():
107
- _object = global_config.defined_variables.get(instrument)
108
- functions = utils._inspect_class(_object)
109
- forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
110
- else:
111
- if deck:
112
- functions = global_config.deck_snapshot.get(instrument, {})
113
- elif pseudo_deck:
114
- functions = pseudo_deck.get(instrument, {})
115
- forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
116
-
117
- if request.method == 'POST' and "hidden_name" in request.form:
118
- method_name = request.form.get("hidden_name", None)
119
- form = forms.get(method_name) if forms else None
120
- insert_position = request.form.get("drop_target_id", None)
121
-
122
- if form:
123
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
124
- if form.validate_on_submit():
125
- function_name = kwargs.pop("hidden_name")
126
- save_data = kwargs.pop('return', '')
127
-
128
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
129
-
130
- script.eval_list(kwargs, primitive_arg_types)
131
- kwargs = script.validate_variables(kwargs)
132
- action = {"instrument": instrument, "action": function_name,
133
- "args": kwargs,
134
- "return": save_data,
135
- 'arg_types': primitive_arg_types}
136
- script.add_action(action=action, insert_position=insert_position)
137
- else:
138
- flash(form.errors)
139
-
140
- elif request.method == 'POST' and "builtin_name" in request.form:
141
- function_name = request.form.get("builtin_name")
142
- form = forms.get(function_name) if forms else None
143
- insert_position = request.form.get("drop_target_id", None)
144
-
145
- if form:
146
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
147
- if form.validate_on_submit():
148
- logic_type = kwargs.pop('builtin_name')
149
- if 'variable' in kwargs:
150
- try:
151
- script.add_variable(insert_position=insert_position, **kwargs)
152
- except ValueError:
153
- flash("Invalid variable type")
154
- else:
155
- script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
156
- else:
157
- flash(form.errors)
158
-
159
- elif request.method == 'POST' and "workflow_name" in request.form:
160
- workflow_name = request.form.get("workflow_name")
161
- form = forms.get(workflow_name) if forms else None
162
- insert_position = request.form.get("drop_target_id", None)
163
-
164
- if form:
165
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
166
- if form.validate_on_submit():
167
- save_data = kwargs.pop('return', '')
168
-
169
- primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
170
-
171
- script.eval_list(kwargs, primitive_arg_types)
172
- kwargs = script.validate_variables(kwargs)
173
- action = {"instrument": instrument, "action": workflow_name,
174
- "args": kwargs,
175
- "return": save_data,
176
- 'arg_types': primitive_arg_types}
177
- script.add_action(action=action, insert_position=insert_position)
178
- script.add_workflow(**kwargs, insert_position=insert_position)
179
- else:
180
- flash(form.errors)
181
84
 
182
- # toggle autofill, autofill doesn't apply to control flow ops
183
- elif request.method == 'POST' and "autofill" in request.form:
184
- autofill = not autofill
185
- session['autofill'] = autofill
186
- if not instrument == 'flow_control':
187
- forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
85
+ # edit_action_info = session.get("edit_action")
188
86
 
189
- utils.post_script_file(script)
190
87
 
191
88
  exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
192
89
  session['python_code'] = exec_string
193
90
 
194
- design_buttons = create_action_button(script)
195
- return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
196
- script=script, defined_variables=deck_variables,
197
- local_variables=global_config.defined_variables,
198
- forms=forms, buttons=design_buttons, format_name=format_name,
199
- use_llm=enable_llm)
91
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
92
+
93
+ return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
94
+ script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
95
+ local_variables=global_config.defined_variables)
200
96
 
201
97
 
202
- @design.route("/generate_code", methods=['POST'])
98
+ @design.route("/draft/meta", methods=["PATCH"])
203
99
  @login_required
204
- def generate_code():
100
+ def update_script_meta():
205
101
  """
206
- .. :quickref: Text to Code; Generate code from user input and update the design canvas.
102
+ .. :quickref: Workflow Design; update the script metadata.
207
103
 
208
- .. http:post:: /design/generate_code
104
+ .. http:patch:: /draft/meta
209
105
 
210
- :form prompt: user's prompt
211
- :status 200: and then redirects to :http:get:`/experiment/build`
212
- :status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
106
+ Update the script metadata, including the script name and status. If the script name is provided,
107
+ it saves the script with that name. If the status is "finished", it finalizes the script.
213
108
 
109
+ :form name: The name to save the script as.
110
+ :form status: The status of the script (e.g., "finished").
111
+
112
+ :status 200: Successfully updated the script metadata.
214
113
  """
215
- agent = global_config.agent
216
- enable_llm = current_app.config["ENABLE_LLM"]
217
- instrument = request.form.get("instrument")
218
-
219
- if request.method == 'POST' and "clear" in request.form:
220
- session['prompt'][instrument] = ''
221
- if request.method == 'POST' and "gen" in request.form:
222
- prompt = request.form.get("prompt")
223
- session['prompt'][instrument] = prompt
224
- sdl_module = global_config.deck_snapshot.get(instrument, {})
225
- empty_script = Script(author=session.get('user'))
226
- if enable_llm and agent is None:
227
- try:
228
- model = current_app.config["LLM_MODEL"]
229
- server = current_app.config["LLM_SERVER"]
230
- module = current_app.config["MODULE"]
231
- from ivoryos.utils.llm_agent import LlmAgent
232
- agent = LlmAgent(host=server, model=model, output_path=os.path.dirname(os.path.abspath(module)))
233
- except Exception as e:
234
- flash(e.__str__())
235
- return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True)), 400
236
- action_list = agent.generate_code(sdl_module, prompt)
237
- for action in action_list:
238
- action['instrument'] = instrument
239
- action['return'] = ''
240
- if "args" not in action:
241
- action['args'] = {}
242
- if "arg_types" not in action:
243
- action['arg_types'] = {}
244
- empty_script.add_action(action)
245
- utils.post_script_file(empty_script)
246
- return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
247
-
248
-
249
-
250
- @design.route("/script/toggle/<stype>")
251
- @login_required
252
- def toggle_script_type(stype=None):
253
- """
254
- .. :quickref: Workflow Design; toggle the experimental phase for design canvas.
114
+ data = request.get_json()
115
+ script = utils.get_script_file()
116
+ if 'name' in data:
117
+ run_name = data.get("name")
118
+ exist_script = Script.query.get(run_name)
119
+ if exist_script is None:
120
+ script.save_as(run_name)
121
+ utils.post_script_file(script)
122
+ return jsonify(success=True)
123
+ else:
124
+ flash("Script name is already exist in database")
125
+ return jsonify(success=False)
255
126
 
256
- .. http:get:: /design/script/toggle/<stype>
127
+ if 'status' in data:
128
+ if data['status'] == "finished":
129
+ script.finalize()
130
+ utils.post_script_file(script)
131
+ return jsonify(success=True)
132
+ return jsonify(success=False)
257
133
 
258
- :status 200: and then redirects to :http:get:`/design/script`
259
134
 
135
+ @design.route("/draft/ui-state", methods=["PATCH"])
136
+ @login_required
137
+ def update_ui_state():
260
138
  """
261
- script = utils.get_script_file()
262
- script.editing_type = stype
263
- utils.post_script_file(script)
264
- return redirect(url_for('design.experiment_builder'))
139
+ .. :quickref: Workflow Design; update the UI state for the design canvas.
265
140
 
141
+ .. http:patch:: /draft/ui-state
266
142
 
267
- @design.route("/updateList", methods=['POST'])
268
- @login_required
269
- def update_list():
270
- order = request.form['order']
271
- script = utils.get_script_file()
272
- script.currently_editing_order = order.split(",", len(script.currently_editing_script))
273
- script.sort_actions()
274
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
275
- utils.post_script_file(script)
276
- session['python_code'] = exec_string
143
+ Update the UI state for the design canvas, including showing code overlays, setting editing types,
144
+ and handling deck selection.
277
145
 
278
- return jsonify({'success': True})
146
+ :form show_code: Whether to show the code overlay (true/false).
147
+ :form editing_type: The type of editing to set (prep, script, cleanup).
148
+ :form autofill: Whether to enable autofill for the instrument panel (true/false).
149
+ :form deck_name: The name of the deck to select.
279
150
 
151
+ :status 200: Updates the UI state and returns a success message.
152
+ """
153
+ data = request.get_json()
280
154
 
281
- @design.route("/toggle_show_code", methods=["POST"])
282
- def toggle_show_code():
283
- session["show_code"] = not session.get("show_code", False)
284
- return redirect(request.referrer or url_for("design.experiment_builder"))
155
+ if "show_code" in data:
156
+ session["show_code"] = bool(data["show_code"])
157
+ return jsonify({"success": True})
158
+ if "editing_type" in data:
159
+ stype = data.get("editing_type")
285
160
 
161
+ script = utils.get_script_file()
162
+ script.editing_type = stype
163
+ utils.post_script_file(script)
286
164
 
287
- # --------------------handle all the import/export and download/upload--------------------------
288
- @design.route("/clear")
165
+ # Re-render only the part of the page you want to update
166
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
167
+ rendered_html = render_template("components/canvas.html", script=script, buttons_dict=design_buttons)
168
+ return jsonify({"html": rendered_html})
169
+
170
+ if "autofill" in data:
171
+ script = utils.get_script_file()
172
+ instrument = data.get("instrument", '')
173
+ autofill = data.get("autofill", False)
174
+ session['autofill'] = autofill
175
+ _, forms = _create_forms(instrument, script, autofill)
176
+ rendered_html = render_template("components/methods_panel.html", forms=forms, script=script, instrument=instrument)
177
+ return jsonify({"html": rendered_html})
178
+
179
+ if "deck_name" in data:
180
+ pkl_name = data.get('deck_name', "")
181
+ script = utils.get_script_file()
182
+ session['pseudo_deck'] = pkl_name
183
+ deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
184
+
185
+ if script.deck is None or script.isEmpty():
186
+ script.deck = pkl_name.split('.')[0]
187
+ utils.post_script_file(script)
188
+ elif script.deck and not script.deck == pkl_name.split('.')[0]:
189
+ flash(f"Choose the deck with name {script.deck}")
190
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pkl_name)
191
+ pseudo_deck = utils.load_deck(pseudo_deck_path)
192
+ deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
193
+ deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
194
+ html = render_template("components/sidebar.html", history=deck_list,
195
+ defined_variables=deck_variables, local_variables = global_config.defined_variables)
196
+ return jsonify({"html": html})
197
+ return jsonify({"error": "Invalid request"}), 400
198
+
199
+
200
+ # @design.route("/draft/steps/order", methods=['POST'])
201
+ # @login_required
202
+ # def update_list():
203
+ # """
204
+ # .. :quickref: Workflow Design Steps; update the order of steps in the design canvas when reordering steps.
205
+ #
206
+ # .. http:post:: /draft/steps/order
207
+ #
208
+ # Update the order of steps in the design canvas when reordering steps.
209
+ #
210
+ # :form order: A comma-separated string representing the new order of steps.
211
+ # :status 200: Successfully updated the order of steps.
212
+ # """
213
+ # order = request.form['order']
214
+ # script = utils.get_script_file()
215
+ # script.currently_editing_order = order.split(",", len(script.currently_editing_script))
216
+ # script.sort_actions()
217
+ # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
218
+ # utils.post_script_file(script)
219
+ # session['python_code'] = exec_string
220
+ #
221
+ # return jsonify({'success': True})
222
+
223
+
224
+
225
+ @design.route("/draft", methods=['DELETE'])
289
226
  @login_required
290
- def clear():
227
+ def clear_draft():
291
228
  """
292
229
  .. :quickref: Workflow Design; clear the design canvas.
293
230
 
294
- .. http:get:: /design/clear
231
+ .. http:delete:: /draft
295
232
 
296
- :form prompt: user's prompt
297
- :status 200: clear canvas and then redirects to :http:get:`/design/script`
233
+ :status 200: clear canvas
298
234
  """
299
235
  deck = global_config.deck
300
- pseudo_name = session.get("pseudo_deck", "")
301
236
  if deck:
302
237
  deck_name = os.path.splitext(os.path.basename(deck.__file__))[
303
238
  0] if deck.__name__ == "__main__" else deck.__name__
304
- elif pseudo_name:
305
- deck_name = pseudo_name
306
239
  else:
307
- deck_name = ''
240
+ deck_name = session.get("pseudo_deck", "")
308
241
  script = Script(deck=deck_name, author=session.get('username'))
309
242
  utils.post_script_file(script)
310
- return redirect(url_for("design.experiment_builder"))
243
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
244
+ session['python_code'] = exec_string
245
+ return jsonify({'success': True})
311
246
 
312
247
 
313
- @design.route("/import/pseudo", methods=['POST'])
314
- @login_required
315
- def import_pseudo():
316
- """
317
- .. :quickref: Workflow Design; Import pseudo deck from deck history
318
248
 
319
- .. http:post:: /design/import/pseudo
320
249
 
321
- :form pkl_name: pseudo deck name
322
- :status 302: load pseudo deck and then redirects to :http:get:`/design/script`
323
- """
324
- pkl_name = request.form.get('pkl_name')
325
- script = utils.get_script_file()
326
- session['pseudo_deck'] = pkl_name
327
250
 
328
- if script.deck is None or script.isEmpty():
329
- script.deck = pkl_name.split('.')[0]
330
- utils.post_script_file(script)
331
- elif script.deck and not script.deck == pkl_name.split('.')[0]:
332
- flash(f"Choose the deck with name {script.deck}")
333
- return redirect(url_for("design.experiment_builder"))
251
+ @design.route("/draft/submit_python", methods=["POST"])
252
+ def submit_script():
253
+ """
254
+ .. :quickref: Workflow Design; convert Python to workflow script
334
255
 
256
+ .. http:post:: /design/submit_python
335
257
 
258
+ Convert a Python script to a workflow script and save it in the database.
336
259
 
337
- @design.route("/submit_python", methods=["POST"])
338
- def submit_script():
339
- """Submit script"""
260
+ :form workflow_name: workflow name
261
+ :form script: main script
262
+ :form prep: prep script
263
+ :form cleanup: post script
264
+ :status 200: clear canvas
265
+ """
340
266
  deck = global_config.deck
341
267
  deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
342
268
  script = Script(author=session.get('user'), deck=deck_name)
@@ -352,14 +278,151 @@ def submit_script():
352
278
  script.script_dict[stype] = card
353
279
  result[stype] = "success"
354
280
  except Exception as e:
355
- result[
356
- stype] = f"failed to transcript to ivoryos visualization, but function can still run. error: {str(e)}"
281
+ result[stype] = f"failed to transcript, but function can still run. error: {str(e)}"
357
282
  utils.post_script_file(script)
358
- try:
359
- publish()
360
- db_status = "success"
361
- except Exception as e:
362
- db_status = "failed"
363
- return jsonify({"script": result, "db": db_status}), 200
283
+ status = publish()
284
+ return jsonify({"script": result, "db": status}), 200
285
+
286
+
287
+
288
+ @design.post("/draft/instruments/<string:instrument>")
289
+ @login_required
290
+ def methods_handler(instrument: str = ''):
291
+ """
292
+ .. :quickref: Workflow Design; handle methods of a specific instrument
293
+
294
+ .. http:post:: /draft/instruments/<string:instrument>
295
+
296
+ Add methods for a specific instrument in the workflow design.
297
+
298
+ :param instrument: The name of the instrument to handle methods for.
299
+ :type instrument: str
300
+ :status 200: Render the methods for the specified instrument.
301
+ """
302
+ script = utils.get_script_file()
303
+ pseudo_deck_name = session.get('pseudo_deck', '')
304
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
305
+ off_line = current_app.config["OFF_LINE"]
306
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
307
+ autofill = session.get('autofill', False)
308
+
309
+ functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
310
+
311
+ success = True
312
+ msg = ""
313
+ if "hidden_name" in request.form:
314
+ method_name = request.form.get("hidden_name", None)
315
+ form = forms.get(method_name) if forms else None
316
+ insert_position = request.form.get("drop_target_id", None)
317
+ if form:
318
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
319
+ if form.validate_on_submit():
320
+ function_name = kwargs.pop("hidden_name")
321
+ save_data = kwargs.pop('return', '')
322
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
323
+
324
+ # todo
325
+ print(primitive_arg_types)
326
+
327
+ script.eval_list(kwargs, primitive_arg_types)
328
+ kwargs = script.validate_variables(kwargs)
329
+ action = {"instrument": instrument, "action": function_name,
330
+ "args": kwargs,
331
+ "return": save_data,
332
+ 'arg_types': primitive_arg_types}
333
+ script.add_action(action=action, insert_position=insert_position)
334
+ else:
335
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
336
+ success = False
337
+ elif "builtin_name" in request.form:
338
+ function_name = request.form.get("builtin_name")
339
+ form = forms.get(function_name) if forms else None
340
+ insert_position = request.form.get("drop_target_id", None)
341
+ if form:
342
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
343
+ if form.validate_on_submit():
344
+ logic_type = kwargs.pop('builtin_name')
345
+ if 'variable' in kwargs:
346
+ try:
347
+ script.add_variable(insert_position=insert_position, **kwargs)
348
+ except ValueError:
349
+ success = False
350
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
351
+ else:
352
+ script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
353
+ else:
354
+ success = False
355
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
356
+ elif "workflow_name" in request.form:
357
+ workflow_name = request.form.get("workflow_name")
358
+ form = forms.get(workflow_name) if forms else None
359
+ insert_position = request.form.get("drop_target_id", None)
360
+ if form:
361
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
362
+ if form.validate_on_submit():
363
+ save_data = kwargs.pop('return', '')
364
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
365
+ script.eval_list(kwargs, primitive_arg_types)
366
+ kwargs = script.validate_variables(kwargs)
367
+ action = {"instrument": instrument, "action": workflow_name,
368
+ "args": kwargs,
369
+ "return": save_data,
370
+ 'arg_types': primitive_arg_types}
371
+ script.add_action(action=action, insert_position=insert_position)
372
+ script.add_workflow(**kwargs, insert_position=insert_position)
373
+ else:
374
+ success = False
375
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
376
+ utils.post_script_file(script)
377
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
378
+ session['python_code'] = exec_string
379
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
380
+ html = render_template("components/canvas_main.html", script=script, buttons_dict=design_buttons)
381
+ return jsonify({"html": html, "success": success, "error": msg})
382
+
383
+
384
+ @design.get("/draft/instruments", strict_slashes=False)
385
+ @design.get("/draft/instruments/<string:instrument>")
386
+ @login_required
387
+ def get_operation_sidebar(instrument: str = ''):
388
+ """
389
+ .. :quickref: Workflow Design; handle methods of a specific instrument
390
+
391
+ .. http:get:: /design/instruments/<string:instrument>
392
+
393
+ :param instrument: The name of the instrument to handle methods for.
394
+ :type instrument: str
395
+
396
+ :status 200: Render the methods for the specified instrument.
397
+ """
398
+ script = utils.get_script_file()
399
+ pseudo_deck_name = session.get('pseudo_deck', '')
400
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
401
+ off_line = current_app.config["OFF_LINE"]
402
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
403
+ autofill = session.get('autofill', False)
404
+
405
+ functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
406
+
407
+ if instrument:
408
+ html = render_template("components/sidebar.html", forms=forms, instrument=instrument, script=script)
409
+ else:
410
+ pseudo_deck_name = session.get('pseudo_deck', '')
411
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
412
+ off_line = current_app.config["OFF_LINE"]
413
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
414
+ if off_line and pseudo_deck is None:
415
+ flash("Choose available deck below.")
416
+ deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
417
+ if not off_line:
418
+ deck_variables = list(global_config.deck_snapshot.keys())
419
+ else:
420
+ deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
421
+ deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
422
+ # edit_action_info = session.get("edit_action")
423
+ html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
424
+ defined_variables=deck_variables,
425
+ local_variables=global_config.defined_variables)
426
+ return jsonify({"html": html})
364
427
 
365
428