ivoryos 1.0.9__py3-none-any.whl → 1.2.0b1__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.
- ivoryos/__init__.py +26 -7
- ivoryos/routes/api/api.py +56 -0
- ivoryos/routes/auth/auth.py +5 -5
- ivoryos/routes/control/control.py +77 -372
- ivoryos/routes/control/control_file.py +36 -0
- ivoryos/routes/control/control_new_device.py +142 -0
- ivoryos/routes/control/templates/controllers.html +166 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +38 -0
- ivoryos/routes/data/data.py +129 -0
- ivoryos/routes/data/templates/components/step_card.html +13 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +298 -656
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +145 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +34 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +317 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +399 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +159 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +30 -22
- ivoryos/routes/main/main.py +1 -1
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/js/action_handlers.js +213 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/sortable_design.js +89 -56
- ivoryos/static/js/ui_state.js +113 -0
- ivoryos/templates/base.html +4 -4
- ivoryos/utils/bo_campaign.py +179 -3
- ivoryos/utils/db_models.py +14 -5
- ivoryos/utils/form.py +5 -9
- ivoryos/utils/global_config.py +13 -1
- ivoryos/utils/py_to_json.py +225 -0
- ivoryos/utils/script_runner.py +49 -7
- ivoryos/utils/serilize.py +203 -0
- ivoryos/utils/task_runner.py +4 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/METADATA +5 -8
- ivoryos-1.2.0b1.dist-info/RECORD +105 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/WHEEL +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/top_level.txt +0 -0
ivoryos/routes/design/design.py
CHANGED
|
@@ -1,786 +1,428 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
import json
|
|
3
1
|
import os
|
|
4
|
-
import pickle
|
|
5
|
-
import sys
|
|
6
|
-
import time
|
|
7
2
|
|
|
8
|
-
from flask import Blueprint, redirect, url_for, flash, jsonify,
|
|
9
|
-
current_app, g
|
|
3
|
+
from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, current_app
|
|
10
4
|
from flask_login import login_required
|
|
11
|
-
from flask_socketio import SocketIO
|
|
12
|
-
from werkzeug.utils import secure_filename
|
|
13
5
|
|
|
6
|
+
from ivoryos.routes.library.library import publish
|
|
14
7
|
from ivoryos.utils import utils
|
|
15
8
|
from ivoryos.utils.global_config import GlobalConfig
|
|
16
|
-
from ivoryos.utils.form import
|
|
17
|
-
|
|
18
|
-
from ivoryos.utils.
|
|
19
|
-
from ivoryos.utils.script_runner import ScriptRunner
|
|
20
|
-
# from ivoryos.utils.utils import load_workflows
|
|
9
|
+
from ivoryos.utils.form import create_action_button, create_form_from_pseudo, create_all_builtin_forms
|
|
10
|
+
from ivoryos.utils.db_models import Script
|
|
11
|
+
from ivoryos.utils.py_to_json import convert_to_cards
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
# Import the new modular components
|
|
14
|
+
from ivoryos.routes.design.design_file import files
|
|
15
|
+
from ivoryos.routes.design.design_step import steps
|
|
24
16
|
|
|
25
|
-
global_config = GlobalConfig()
|
|
26
|
-
runner = ScriptRunner()
|
|
27
|
-
|
|
28
|
-
def abort_pending():
|
|
29
|
-
runner.abort_pending()
|
|
30
|
-
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
31
|
-
|
|
32
|
-
def abort_current():
|
|
33
|
-
runner.stop_execution()
|
|
34
|
-
socketio.emit('log', {'message': "stopped next task"})
|
|
35
|
-
|
|
36
|
-
def pause():
|
|
37
|
-
runner.retry = False
|
|
38
|
-
msg = runner.toggle_pause()
|
|
39
|
-
socketio.emit('log', {'message': msg})
|
|
40
|
-
return msg
|
|
41
|
-
|
|
42
|
-
def retry():
|
|
43
|
-
runner.retry = True
|
|
44
|
-
msg = runner.toggle_pause()
|
|
45
|
-
socketio.emit('log', {'message': msg})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# ---- Socket.IO Event Handlers ----
|
|
49
|
-
|
|
50
|
-
@socketio.on('abort_pending')
|
|
51
|
-
def handle_abort_pending():
|
|
52
|
-
abort_pending()
|
|
53
17
|
|
|
54
|
-
|
|
55
|
-
def handle_abort_current():
|
|
56
|
-
abort_current()
|
|
18
|
+
design = Blueprint('design', __name__, template_folder='templates')
|
|
57
19
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@socketio.on('retry')
|
|
63
|
-
def handle_retry():
|
|
64
|
-
retry()
|
|
20
|
+
# Register sub-blueprints
|
|
21
|
+
design.register_blueprint(files)
|
|
22
|
+
design.register_blueprint(steps)
|
|
65
23
|
|
|
24
|
+
global_config = GlobalConfig()
|
|
66
25
|
|
|
67
|
-
|
|
68
|
-
def handle_abort_action():
|
|
69
|
-
# Fetch log messages from local file
|
|
70
|
-
filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
|
|
71
|
-
with open(filename, 'r') as log_file:
|
|
72
|
-
log_history = log_file.readlines()
|
|
73
|
-
for message in log_history[-10:]:
|
|
74
|
-
socketio.emit('log', {'message': message})
|
|
26
|
+
# ---- Main Design Routes ----
|
|
75
27
|
|
|
76
28
|
|
|
77
|
-
|
|
78
|
-
|
|
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")
|
|
79
47
|
@login_required
|
|
80
|
-
def experiment_builder(
|
|
48
|
+
def experiment_builder():
|
|
81
49
|
"""
|
|
82
50
|
.. :quickref: Workflow Design; Build experiment workflow
|
|
83
51
|
|
|
84
52
|
**Experiment Builder**
|
|
85
53
|
|
|
86
|
-
|
|
87
|
-
define variables, and manage experiment scripts.
|
|
88
|
-
|
|
89
|
-
.. http:get:: /design/script
|
|
54
|
+
.. http:get:: /draft
|
|
90
55
|
|
|
91
|
-
Load the experiment builder
|
|
56
|
+
Load the experiment builder page where users can design their workflow by adding actions, instruments, and logic.
|
|
92
57
|
|
|
93
|
-
:param instrument: The specific instrument for which to load functions and forms.
|
|
94
|
-
:type instrument: str
|
|
95
58
|
:status 200: Experiment builder loaded successfully.
|
|
96
59
|
|
|
97
|
-
.. http:post:: /design/script
|
|
98
|
-
|
|
99
|
-
Submit form data to add or modify actions in the experiment script.
|
|
100
|
-
|
|
101
|
-
**Adding action to canvas**
|
|
102
|
-
|
|
103
|
-
:form return: (optional) The name of the function or method to add to the script.
|
|
104
|
-
:form dynamic: depend on the selected instrument and its metadata.
|
|
105
|
-
|
|
106
|
-
:status 200: Action added or modified successfully.
|
|
107
|
-
:status 400: Validation errors in submitted form data.
|
|
108
|
-
:status 302: Toggles autofill or redirects to refresh the page.
|
|
109
|
-
|
|
110
|
-
**Toggle auto parameter name fill**:
|
|
111
|
-
|
|
112
|
-
:status 200: autofill toggled successfully
|
|
113
|
-
|
|
114
60
|
"""
|
|
115
61
|
deck = global_config.deck
|
|
116
62
|
script = utils.get_script_file()
|
|
117
|
-
# load_workflows(script)
|
|
118
|
-
# registered_workflows = global_config.registered_workflows
|
|
119
63
|
|
|
120
64
|
if deck and script.deck is None:
|
|
121
65
|
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
122
66
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
123
|
-
# script.sort_actions()
|
|
124
67
|
|
|
125
68
|
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
126
69
|
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
127
70
|
off_line = current_app.config["OFF_LINE"]
|
|
128
|
-
enable_llm = current_app.config["ENABLE_LLM"]
|
|
129
|
-
autofill = session.get('autofill')
|
|
130
71
|
|
|
131
|
-
# autofill is not allowed for prep and cleanup
|
|
132
|
-
autofill = autofill if script.editing_type == "script" else False
|
|
133
|
-
forms = None
|
|
134
72
|
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
135
73
|
if off_line and pseudo_deck is None:
|
|
136
74
|
flash("Choose available deck below.")
|
|
137
75
|
|
|
138
76
|
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
139
77
|
|
|
140
|
-
functions = {}
|
|
141
78
|
if deck:
|
|
142
79
|
deck_variables = list(global_config.deck_snapshot.keys())
|
|
143
|
-
# deck_variables.insert(0, "
|
|
144
|
-
deck_variables.insert(0, "flow_control")
|
|
145
|
-
|
|
80
|
+
# deck_variables.insert(0, "flow_control")
|
|
146
81
|
else:
|
|
147
82
|
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
148
83
|
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
149
|
-
edit_action_info = session.get("edit_action")
|
|
150
|
-
if edit_action_info:
|
|
151
|
-
forms = create_form_from_action(edit_action_info, script=script)
|
|
152
|
-
elif instrument:
|
|
153
|
-
# if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
154
|
-
# forms = create_builtin_form(instrument, script=script)
|
|
155
|
-
if instrument == 'flow_control':
|
|
156
|
-
forms = create_all_builtin_forms(script=script)
|
|
157
|
-
# elif instrument == 'registered_workflows':
|
|
158
|
-
# functions = utils._inspect_class(registered_workflows)
|
|
159
|
-
# # forms = create_workflow_forms(script=script)
|
|
160
|
-
# forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
161
|
-
elif instrument in global_config.defined_variables.keys():
|
|
162
|
-
_object = global_config.defined_variables.get(instrument)
|
|
163
|
-
functions = utils._inspect_class(_object)
|
|
164
|
-
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
165
|
-
else:
|
|
166
|
-
if deck:
|
|
167
|
-
functions = global_config.deck_snapshot.get(instrument, {})
|
|
168
|
-
elif pseudo_deck:
|
|
169
|
-
functions = pseudo_deck.get(instrument, {})
|
|
170
|
-
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
171
|
-
if request.method == 'POST' and "hidden_name" in request.form:
|
|
172
|
-
# all_kwargs = request.form.copy()
|
|
173
|
-
method_name = request.form.get("hidden_name", None)
|
|
174
|
-
# if method_name is not None:
|
|
175
|
-
form = forms.get(method_name)
|
|
176
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
177
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
178
|
-
if form and form.validate_on_submit():
|
|
179
|
-
function_name = kwargs.pop("hidden_name")
|
|
180
|
-
save_data = kwargs.pop('return', '')
|
|
181
|
-
|
|
182
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
183
84
|
|
|
184
|
-
|
|
185
|
-
kwargs = script.validate_variables(kwargs)
|
|
186
|
-
action = {"instrument": instrument, "action": function_name,
|
|
187
|
-
"args": kwargs,
|
|
188
|
-
"return": save_data,
|
|
189
|
-
'arg_types': primitive_arg_types}
|
|
190
|
-
script.add_action(action=action, insert_position=insert_position)
|
|
191
|
-
else:
|
|
192
|
-
flash(form.errors)
|
|
193
|
-
|
|
194
|
-
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
195
|
-
function_name = request.form.get("builtin_name")
|
|
196
|
-
form = forms.get(function_name)
|
|
197
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
198
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
199
|
-
|
|
200
|
-
if form.validate_on_submit():
|
|
201
|
-
# print(kwargs)
|
|
202
|
-
logic_type = kwargs.pop('builtin_name')
|
|
203
|
-
if 'variable' in kwargs:
|
|
204
|
-
try:
|
|
205
|
-
script.add_variable(insert_position=insert_position, **kwargs)
|
|
206
|
-
except ValueError:
|
|
207
|
-
flash("Invalid variable type")
|
|
208
|
-
else:
|
|
209
|
-
script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
|
|
210
|
-
else:
|
|
211
|
-
flash(form.errors)
|
|
212
|
-
elif request.method == 'POST' and "workflow_name" in request.form:
|
|
213
|
-
workflow_name = request.form.get("workflow_name")
|
|
214
|
-
form = forms.get(workflow_name)
|
|
215
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
216
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
85
|
+
# edit_action_info = session.get("edit_action")
|
|
217
86
|
|
|
218
|
-
if form.validate_on_submit():
|
|
219
|
-
# workflow_name = kwargs.pop('workflow_name')
|
|
220
|
-
save_data = kwargs.pop('return', '')
|
|
221
|
-
|
|
222
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
|
|
223
|
-
|
|
224
|
-
script.eval_list(kwargs, primitive_arg_types)
|
|
225
|
-
kwargs = script.validate_variables(kwargs)
|
|
226
|
-
action = {"instrument": instrument, "action": workflow_name,
|
|
227
|
-
"args": kwargs,
|
|
228
|
-
"return": save_data,
|
|
229
|
-
'arg_types': primitive_arg_types}
|
|
230
|
-
script.add_action(action=action, insert_position=insert_position)
|
|
231
|
-
script.add_workflow(**kwargs, insert_position=insert_position)
|
|
232
|
-
else:
|
|
233
|
-
flash(form.errors)
|
|
234
|
-
|
|
235
|
-
# toggle autofill, autofill doesn't apply to control flow ops
|
|
236
|
-
elif request.method == 'POST' and "autofill" in request.form:
|
|
237
|
-
autofill = not autofill
|
|
238
|
-
session['autofill'] = autofill
|
|
239
|
-
if not instrument == 'flow_control':
|
|
240
|
-
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
241
|
-
|
|
242
|
-
utils.post_script_file(script)
|
|
243
87
|
|
|
244
88
|
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
245
89
|
session['python_code'] = exec_string
|
|
246
90
|
|
|
247
|
-
design_buttons = create_action_button(script)
|
|
248
|
-
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
249
|
-
script=script, defined_variables=deck_variables,
|
|
250
|
-
local_variables=global_config.defined_variables,
|
|
251
|
-
forms=forms, buttons=design_buttons, format_name=format_name,
|
|
252
|
-
use_llm=enable_llm)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
@design.route("/design/generate_code", methods=['POST'])
|
|
256
|
-
@login_required
|
|
257
|
-
def generate_code():
|
|
258
|
-
"""
|
|
259
|
-
.. :quickref: Text to Code; Generate code from user input and update the design canvas.
|
|
91
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
260
92
|
|
|
261
|
-
|
|
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)
|
|
262
96
|
|
|
263
|
-
:form prompt: user's prompt
|
|
264
|
-
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
265
|
-
:status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
|
|
266
97
|
|
|
267
|
-
|
|
268
|
-
agent = global_config.agent
|
|
269
|
-
enable_llm = current_app.config["ENABLE_LLM"]
|
|
270
|
-
instrument = request.form.get("instrument")
|
|
271
|
-
|
|
272
|
-
if request.method == 'POST' and "clear" in request.form:
|
|
273
|
-
session['prompt'][instrument] = ''
|
|
274
|
-
if request.method == 'POST' and "gen" in request.form:
|
|
275
|
-
prompt = request.form.get("prompt")
|
|
276
|
-
session['prompt'][instrument] = prompt
|
|
277
|
-
# sdl_module = utils.parse_functions(find_instrument_by_name(f'deck.{instrument}'), doc_string=True)
|
|
278
|
-
sdl_module = global_config.deck_snapshot.get(instrument, {})
|
|
279
|
-
empty_script = Script(author=session.get('user'))
|
|
280
|
-
if enable_llm and agent is None:
|
|
281
|
-
try:
|
|
282
|
-
model = current_app.config["LLM_MODEL"]
|
|
283
|
-
server = current_app.config["LLM_SERVER"]
|
|
284
|
-
module = current_app.config["MODULE"]
|
|
285
|
-
from ivoryos.utils.llm_agent import LlmAgent
|
|
286
|
-
agent = LlmAgent(host=server, model=model, output_path=os.path.dirname(os.path.abspath(module)))
|
|
287
|
-
except Exception as e:
|
|
288
|
-
flash(e.__str__())
|
|
289
|
-
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True)), 400
|
|
290
|
-
action_list = agent.generate_code(sdl_module, prompt)
|
|
291
|
-
for action in action_list:
|
|
292
|
-
action['instrument'] = instrument
|
|
293
|
-
action['return'] = ''
|
|
294
|
-
if "args" not in action:
|
|
295
|
-
action['args'] = {}
|
|
296
|
-
if "arg_types" not in action:
|
|
297
|
-
action['arg_types'] = {}
|
|
298
|
-
empty_script.add_action(action)
|
|
299
|
-
utils.post_script_file(empty_script)
|
|
300
|
-
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@design.route("/design/campaign", methods=['GET', 'POST'])
|
|
98
|
+
@design.route("/draft/meta", methods=["PATCH"])
|
|
304
99
|
@login_required
|
|
305
|
-
def
|
|
100
|
+
def update_script_meta():
|
|
306
101
|
"""
|
|
307
|
-
.. :quickref: Workflow
|
|
308
|
-
|
|
309
|
-
.. http:get:: /design/campaign
|
|
102
|
+
.. :quickref: Workflow Design; update the script metadata.
|
|
310
103
|
|
|
311
|
-
|
|
104
|
+
.. http:patch:: /draft/meta
|
|
312
105
|
|
|
313
|
-
|
|
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.
|
|
314
108
|
|
|
315
|
-
|
|
109
|
+
:form name: The name to save the script as.
|
|
110
|
+
:form status: The status of the script (e.g., "finished").
|
|
316
111
|
|
|
112
|
+
:status 200: Successfully updated the script metadata.
|
|
317
113
|
"""
|
|
318
|
-
|
|
319
|
-
script = utils.get_script_file()
|
|
320
|
-
|
|
321
|
-
# script.sort_actions() # handled in update list
|
|
322
|
-
off_line = current_app.config["OFF_LINE"]
|
|
323
|
-
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
324
|
-
# if not off_line and deck is None:
|
|
325
|
-
# # print("loading deck")
|
|
326
|
-
# module = current_app.config.get('MODULE', '')
|
|
327
|
-
# deck = sys.modules[module] if module else None
|
|
328
|
-
# script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
329
|
-
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
330
|
-
config_preview = []
|
|
331
|
-
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
332
|
-
try:
|
|
333
|
-
# todo
|
|
334
|
-
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
335
|
-
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
336
|
-
# print(exec_string)
|
|
337
|
-
except Exception as e:
|
|
338
|
-
flash(e.__str__())
|
|
339
|
-
# handle api request
|
|
340
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
341
|
-
return jsonify({"error": e.__str__()})
|
|
342
|
-
else:
|
|
343
|
-
return redirect(url_for("design.experiment_builder"))
|
|
344
|
-
|
|
345
|
-
config_file = request.args.get("filename")
|
|
346
|
-
config = []
|
|
347
|
-
if config_file:
|
|
348
|
-
session['config_file'] = config_file
|
|
349
|
-
filename = session.get("config_file")
|
|
350
|
-
if filename:
|
|
351
|
-
# config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
352
|
-
config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
353
|
-
config_preview = config[1:]
|
|
354
|
-
arg_type = config.pop(0) # first entry is types
|
|
355
|
-
try:
|
|
356
|
-
for key, func_str in exec_string.items():
|
|
357
|
-
exec(func_str)
|
|
358
|
-
line_collection = script.convert_to_lines(exec_string)
|
|
359
|
-
|
|
360
|
-
except Exception:
|
|
361
|
-
flash(f"Please check {key} syntax!!")
|
|
362
|
-
return redirect(url_for("design.experiment_builder"))
|
|
363
|
-
# runner.globals_dict.update(globals())
|
|
364
|
-
run_name = script.name if script.name else "untitled"
|
|
365
|
-
|
|
366
|
-
dismiss = session.get("dismiss", None)
|
|
114
|
+
data = request.get_json()
|
|
367
115
|
script = utils.get_script_file()
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if deck is None:
|
|
376
|
-
no_deck_warning = True
|
|
377
|
-
flash(f"No deck is found, import {script.deck}")
|
|
378
|
-
elif script.deck:
|
|
379
|
-
is_deck_match = script.deck == deck.__name__ or script.deck == \
|
|
380
|
-
os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
381
|
-
if not is_deck_match:
|
|
382
|
-
flash(f"This script is not compatible with current deck, import {script.deck}")
|
|
383
|
-
if request.method == "POST":
|
|
384
|
-
bo_args = None
|
|
385
|
-
compiled = False
|
|
386
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
387
|
-
payload_json = request.get_json()
|
|
388
|
-
compiled = True
|
|
389
|
-
if "kwargs" in payload_json:
|
|
390
|
-
config = payload_json["kwargs"]
|
|
391
|
-
elif "parameters" in payload_json:
|
|
392
|
-
bo_args = payload_json
|
|
393
|
-
repeat = payload_json.pop("repeat", None)
|
|
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)
|
|
394
123
|
else:
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if "online-config" in request.form:
|
|
398
|
-
config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
|
|
399
|
-
repeat = request.form.get('repeat', None)
|
|
124
|
+
flash("Script name is already exist in database")
|
|
125
|
+
return jsonify(success=False)
|
|
400
126
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
current_app=current_app._get_current_object()
|
|
408
|
-
)
|
|
409
|
-
if utils.check_config_duplicate(config):
|
|
410
|
-
flash(f"WARNING: Duplicate in config entries.")
|
|
411
|
-
except Exception as e:
|
|
412
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
413
|
-
return jsonify({"error": e.__str__()})
|
|
414
|
-
else:
|
|
415
|
-
flash(e)
|
|
416
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
417
|
-
# wait to get a workflow ID
|
|
418
|
-
while not global_config.runner_status:
|
|
419
|
-
time.sleep(1)
|
|
420
|
-
return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
|
|
421
|
-
else:
|
|
422
|
-
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
423
|
-
dot_py=exec_string, line_collection=line_collection,
|
|
424
|
-
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
425
|
-
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
426
|
-
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
427
|
-
history=deck_list, pause_status=runner.pause_status())
|
|
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)
|
|
428
133
|
|
|
429
134
|
|
|
430
|
-
@design.route("/
|
|
135
|
+
@design.route("/draft/ui-state", methods=["PATCH"])
|
|
431
136
|
@login_required
|
|
432
|
-
def
|
|
137
|
+
def update_ui_state():
|
|
433
138
|
"""
|
|
434
|
-
.. :quickref: Workflow Design;
|
|
139
|
+
.. :quickref: Workflow Design; update the UI state for the design canvas.
|
|
435
140
|
|
|
436
|
-
.. http:
|
|
141
|
+
.. http:patch:: /draft/ui-state
|
|
437
142
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
"""
|
|
441
|
-
script = utils.get_script_file()
|
|
442
|
-
script.editing_type = stype
|
|
443
|
-
utils.post_script_file(script)
|
|
444
|
-
return redirect(url_for('design.experiment_builder'))
|
|
143
|
+
Update the UI state for the design canvas, including showing code overlays, setting editing types,
|
|
144
|
+
and handling deck selection.
|
|
445
145
|
|
|
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.
|
|
446
150
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
order = request.form['order']
|
|
451
|
-
script = utils.get_script_file()
|
|
452
|
-
script.currently_editing_order = order.split(",", len(script.currently_editing_script))
|
|
453
|
-
script.sort_actions()
|
|
454
|
-
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
455
|
-
utils.post_script_file(script)
|
|
456
|
-
session['python_code'] = exec_string
|
|
457
|
-
|
|
458
|
-
return jsonify({'success': True})
|
|
459
|
-
|
|
151
|
+
:status 200: Updates the UI state and returns a success message.
|
|
152
|
+
"""
|
|
153
|
+
data = request.get_json()
|
|
460
154
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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")
|
|
465
160
|
|
|
161
|
+
script = utils.get_script_file()
|
|
162
|
+
script.editing_type = stype
|
|
163
|
+
utils.post_script_file(script)
|
|
466
164
|
|
|
467
|
-
#
|
|
468
|
-
|
|
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'])
|
|
469
226
|
@login_required
|
|
470
|
-
def
|
|
227
|
+
def clear_draft():
|
|
471
228
|
"""
|
|
472
229
|
.. :quickref: Workflow Design; clear the design canvas.
|
|
473
230
|
|
|
474
|
-
.. http:
|
|
231
|
+
.. http:delete:: /draft
|
|
475
232
|
|
|
476
|
-
:
|
|
477
|
-
:status 200: clear canvas and then redirects to :http:get:`/design/script`
|
|
233
|
+
:status 200: clear canvas
|
|
478
234
|
"""
|
|
479
235
|
deck = global_config.deck
|
|
480
|
-
pseudo_name = session.get("pseudo_deck", "")
|
|
481
236
|
if deck:
|
|
482
237
|
deck_name = os.path.splitext(os.path.basename(deck.__file__))[
|
|
483
238
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
484
|
-
elif pseudo_name:
|
|
485
|
-
deck_name = pseudo_name
|
|
486
239
|
else:
|
|
487
|
-
deck_name =
|
|
240
|
+
deck_name = session.get("pseudo_deck", "")
|
|
488
241
|
script = Script(deck=deck_name, author=session.get('username'))
|
|
489
242
|
utils.post_script_file(script)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
@design.route("/design/import/pseudo", methods=['POST'])
|
|
494
|
-
@login_required
|
|
495
|
-
def import_pseudo():
|
|
496
|
-
"""
|
|
497
|
-
.. :quickref: Workflow Design; Import pseudo deck from deck history
|
|
243
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
244
|
+
session['python_code'] = exec_string
|
|
245
|
+
return jsonify({'success': True})
|
|
498
246
|
|
|
499
|
-
.. http:post:: /design/import/pseudo
|
|
500
247
|
|
|
501
|
-
:form pkl_name: pseudo deck name
|
|
502
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
503
|
-
"""
|
|
504
|
-
pkl_name = request.form.get('pkl_name')
|
|
505
|
-
script = utils.get_script_file()
|
|
506
|
-
session['pseudo_deck'] = pkl_name
|
|
507
248
|
|
|
508
|
-
if script.deck is None or script.isEmpty():
|
|
509
|
-
script.deck = pkl_name.split('.')[0]
|
|
510
|
-
utils.post_script_file(script)
|
|
511
|
-
elif script.deck and not script.deck == pkl_name.split('.')[0]:
|
|
512
|
-
flash(f"Choose the deck with name {script.deck}")
|
|
513
|
-
return redirect(url_for("design.experiment_builder"))
|
|
514
249
|
|
|
515
250
|
|
|
516
|
-
@design.route(
|
|
517
|
-
|
|
518
|
-
def upload():
|
|
251
|
+
@design.route("/draft/submit_python", methods=["POST"])
|
|
252
|
+
def submit_script():
|
|
519
253
|
"""
|
|
520
|
-
.. :quickref: Workflow
|
|
521
|
-
|
|
522
|
-
.. http:post:: /design/uploads
|
|
254
|
+
.. :quickref: Workflow Design; convert Python to workflow script
|
|
523
255
|
|
|
524
|
-
|
|
525
|
-
:status 302: save csv file and then redirects to :http:get:`/design/campaign`
|
|
526
|
-
"""
|
|
527
|
-
if request.method == "POST":
|
|
528
|
-
f = request.files['file']
|
|
529
|
-
if 'file' not in request.files:
|
|
530
|
-
flash('No file part')
|
|
531
|
-
if f.filename.split('.')[-1] == "csv":
|
|
532
|
-
filename = secure_filename(f.filename)
|
|
533
|
-
f.save(os.path.join(current_app.config['CSV_FOLDER'], filename))
|
|
534
|
-
session['config_file'] = filename
|
|
535
|
-
return redirect(url_for("design.experiment_run"))
|
|
536
|
-
else:
|
|
537
|
-
flash("Config file is in csv format")
|
|
538
|
-
return redirect(url_for("design.experiment_run"))
|
|
256
|
+
.. http:post:: /design/submit_python
|
|
539
257
|
|
|
258
|
+
Convert a Python script to a workflow script and save it in the database.
|
|
540
259
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
|
544
265
|
"""
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
266
|
+
deck = global_config.deck
|
|
267
|
+
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
268
|
+
script = Script(author=session.get('user'), deck=deck_name)
|
|
269
|
+
script_collection = request.get_json()
|
|
270
|
+
workflow_name = script_collection.pop("workflow_name")
|
|
271
|
+
script.python_script = script_collection
|
|
272
|
+
# todo check script format
|
|
273
|
+
script.name = workflow_name
|
|
274
|
+
result = {}
|
|
275
|
+
for stype, py_str in script_collection.items():
|
|
276
|
+
try:
|
|
277
|
+
card = convert_to_cards(py_str)
|
|
278
|
+
script.script_dict[stype] = card
|
|
279
|
+
result[stype] = "success"
|
|
280
|
+
except Exception as e:
|
|
281
|
+
result[stype] = f"failed to transcript, but function can still run. error: {str(e)}"
|
|
282
|
+
utils.post_script_file(script)
|
|
283
|
+
status = publish()
|
|
284
|
+
return jsonify({"script": result, "db": status}), 200
|
|
548
285
|
|
|
549
|
-
"""
|
|
550
|
-
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
551
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
552
286
|
|
|
553
287
|
|
|
554
|
-
@design.
|
|
288
|
+
@design.post("/draft/instruments/<string:instrument>")
|
|
555
289
|
@login_required
|
|
556
|
-
def
|
|
290
|
+
def methods_handler(instrument: str = ''):
|
|
557
291
|
"""
|
|
558
|
-
.. :quickref: Workflow Design
|
|
559
|
-
|
|
560
|
-
.. http:post:: /load_json
|
|
292
|
+
.. :quickref: Workflow Design; handle methods of a specific instrument
|
|
561
293
|
|
|
562
|
-
:
|
|
563
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
564
|
-
"""
|
|
565
|
-
if request.method == "POST":
|
|
566
|
-
f = request.files['file']
|
|
567
|
-
if 'file' not in request.files:
|
|
568
|
-
flash('No file part')
|
|
569
|
-
if f.filename.endswith("json"):
|
|
570
|
-
script_dict = json.load(f)
|
|
571
|
-
utils.post_script_file(script_dict, is_dict=True)
|
|
572
|
-
else:
|
|
573
|
-
flash("Script file need to be JSON file")
|
|
574
|
-
return redirect(url_for("design.experiment_builder"))
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
@design.route('/design/script/download/<filetype>')
|
|
578
|
-
@login_required
|
|
579
|
-
def download(filetype):
|
|
580
|
-
"""
|
|
581
|
-
.. :quickref: Workflow Design Ext; download a workflow design file
|
|
294
|
+
.. http:post:: /draft/instruments/<string:instrument>
|
|
582
295
|
|
|
583
|
-
|
|
296
|
+
Add methods for a specific instrument in the workflow design.
|
|
584
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.
|
|
585
301
|
"""
|
|
586
302
|
script = utils.get_script_file()
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
cfg, cfg_types = script.config("script")
|
|
593
|
-
writer.writerow(cfg)
|
|
594
|
-
writer.writerow(list(cfg_types.values()))
|
|
595
|
-
elif filetype == "script":
|
|
596
|
-
script.sort_actions()
|
|
597
|
-
json_object = json.dumps(script.as_dict())
|
|
598
|
-
filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}.json")
|
|
599
|
-
with open(filepath, "w") as outfile:
|
|
600
|
-
outfile.write(json_object)
|
|
601
|
-
elif filetype == "python":
|
|
602
|
-
filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
|
|
603
|
-
else:
|
|
604
|
-
return "Unsupported file type", 400
|
|
605
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
@design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
|
|
609
|
-
@login_required
|
|
610
|
-
def edit_action(uuid: str):
|
|
611
|
-
"""
|
|
612
|
-
.. :quickref: Workflow Design; edit parameters of an action step on canvas
|
|
613
|
-
|
|
614
|
-
.. http:get:: /design/step/edit/<uuid>
|
|
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)
|
|
615
308
|
|
|
616
|
-
|
|
309
|
+
functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
|
|
617
310
|
|
|
618
|
-
|
|
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])
|
|
619
323
|
|
|
620
|
-
|
|
621
|
-
|
|
324
|
+
# todo
|
|
325
|
+
print(primitive_arg_types)
|
|
622
326
|
|
|
623
|
-
|
|
624
|
-
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
625
|
-
"""
|
|
626
|
-
script = utils.get_script_file()
|
|
627
|
-
action = script.find_by_uuid(uuid)
|
|
628
|
-
session['edit_action'] = action
|
|
629
|
-
|
|
630
|
-
if request.method == "POST" and action is not None:
|
|
631
|
-
forms = create_form_from_action(action, script=script)
|
|
632
|
-
if "back" not in request.form:
|
|
633
|
-
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
634
|
-
# print(kwargs)
|
|
635
|
-
if forms and forms.validate_on_submit():
|
|
636
|
-
save_as = kwargs.pop('return', '')
|
|
327
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
637
328
|
kwargs = script.validate_variables(kwargs)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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)
|
|
641
334
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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()]
|
|
663
376
|
utils.post_script_file(script)
|
|
664
|
-
|
|
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})
|
|
665
382
|
|
|
666
383
|
|
|
667
|
-
@design.
|
|
384
|
+
@design.get("/draft/instruments", strict_slashes=False)
|
|
385
|
+
@design.get("/draft/instruments/<string:instrument>")
|
|
668
386
|
@login_required
|
|
669
|
-
def
|
|
387
|
+
def get_operation_sidebar(instrument: str = ''):
|
|
670
388
|
"""
|
|
671
|
-
.. :quickref: Workflow Design;
|
|
389
|
+
.. :quickref: Workflow Design; handle methods of a specific instrument
|
|
672
390
|
|
|
673
|
-
.. http:get:: /design/
|
|
391
|
+
.. http:get:: /design/instruments/<string:instrument>
|
|
674
392
|
|
|
675
|
-
:param
|
|
676
|
-
:type
|
|
393
|
+
:param instrument: The name of the instrument to handle methods for.
|
|
394
|
+
:type instrument: str
|
|
677
395
|
|
|
678
|
-
:status
|
|
396
|
+
:status 200: Render the methods for the specified instrument.
|
|
679
397
|
"""
|
|
680
|
-
back = request.referrer
|
|
681
398
|
script = utils.get_script_file()
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
# ---- HTTP API Endpoints ----
|
|
688
|
-
|
|
689
|
-
@design.route("/api/runner/status", methods=["GET"])
|
|
690
|
-
def runner_status():
|
|
691
|
-
"""
|
|
692
|
-
.. :quickref: Workflow Design; get the execution status
|
|
693
|
-
|
|
694
|
-
.. http:get:: /api/runner/status
|
|
695
|
-
|
|
696
|
-
:status 200: status
|
|
697
|
-
"""
|
|
698
|
-
runner_busy = global_config.runner_lock.locked()
|
|
699
|
-
status = {"busy": runner_busy}
|
|
700
|
-
task_status = global_config.runner_status
|
|
701
|
-
current_step = {}
|
|
702
|
-
# print(task_status)
|
|
703
|
-
if task_status is not None:
|
|
704
|
-
task_type = task_status["type"]
|
|
705
|
-
task_id = task_status["id"]
|
|
706
|
-
if task_type == "task":
|
|
707
|
-
step = SingleStep.query.get(task_id)
|
|
708
|
-
current_step = step.as_dict()
|
|
709
|
-
if task_type == "workflow":
|
|
710
|
-
workflow = WorkflowRun.query.get(task_id)
|
|
711
|
-
if workflow is not None:
|
|
712
|
-
latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
|
|
713
|
-
if latest_step is not None:
|
|
714
|
-
current_step = latest_step.as_dict()
|
|
715
|
-
status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
|
|
716
|
-
status["current_task"] = current_step
|
|
717
|
-
return jsonify(status), 200
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
@design.route("/api/runner/abort_pending", methods=["POST"])
|
|
722
|
-
def api_abort_pending():
|
|
723
|
-
"""
|
|
724
|
-
.. :quickref: Workflow Design; abort pending action(s) during execution
|
|
725
|
-
|
|
726
|
-
.. http:get:: /api/runner/abort_pending
|
|
727
|
-
|
|
728
|
-
:status 200: {"status": "ok"}
|
|
729
|
-
"""
|
|
730
|
-
abort_pending()
|
|
731
|
-
return jsonify({"status": "ok"}), 200
|
|
732
|
-
|
|
733
|
-
@design.route("/api/runner/abort_current", methods=["POST"])
|
|
734
|
-
def api_abort_current():
|
|
735
|
-
"""
|
|
736
|
-
.. :quickref: Workflow Design; abort right after current action during execution
|
|
737
|
-
|
|
738
|
-
.. http:get:: /api/runner/abort_current
|
|
739
|
-
|
|
740
|
-
:status 200: {"status": "ok"}
|
|
741
|
-
"""
|
|
742
|
-
abort_current()
|
|
743
|
-
return jsonify({"status": "ok"}), 200
|
|
744
|
-
|
|
745
|
-
@design.route("/api/runner/pause", methods=["POST"])
|
|
746
|
-
def api_pause():
|
|
747
|
-
"""
|
|
748
|
-
.. :quickref: Workflow Design; pause during execution
|
|
749
|
-
|
|
750
|
-
.. http:get:: /api/runner/pause
|
|
751
|
-
|
|
752
|
-
:status 200: {"status": "ok"}
|
|
753
|
-
"""
|
|
754
|
-
msg = pause()
|
|
755
|
-
return jsonify({"status": "ok", "pause_status": msg}), 200
|
|
756
|
-
|
|
757
|
-
@design.route("/api/runner/retry", methods=["POST"])
|
|
758
|
-
def api_retry():
|
|
759
|
-
"""
|
|
760
|
-
.. :quickref: Workflow Design; retry when error occur during execution
|
|
761
|
-
|
|
762
|
-
.. http:get:: /api/runner/retry
|
|
763
|
-
|
|
764
|
-
:status 200: {"status": "ok"}
|
|
765
|
-
"""
|
|
766
|
-
retry()
|
|
767
|
-
return jsonify({"status": "ok, retrying failed step"}), 200
|
|
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)
|
|
768
404
|
|
|
405
|
+
functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
|
|
769
406
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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})
|
|
774
427
|
|
|
775
|
-
.. http:get:: /api/design/submit
|
|
776
428
|
|
|
777
|
-
:status 200: {"status": "ok"}
|
|
778
|
-
"""
|
|
779
|
-
deck = global_config.deck
|
|
780
|
-
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
781
|
-
script = Script(author=session.get('user'), deck=deck_name)
|
|
782
|
-
script_collection = request.get_json()
|
|
783
|
-
script.python_script = script_collection
|
|
784
|
-
# todo check script format
|
|
785
|
-
utils.post_script_file(script)
|
|
786
|
-
return jsonify({"status": "ok"}), 200
|