ivoryos 0.1.8__py3-none-any.whl → 0.1.10__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 +118 -99
- ivoryos/config.py +47 -47
- ivoryos/routes/auth/auth.py +100 -65
- ivoryos/routes/auth/templates/auth/login.html +25 -25
- ivoryos/routes/auth/templates/auth/signup.html +32 -32
- ivoryos/routes/control/control.py +400 -272
- ivoryos/routes/control/templates/control/controllers.html +75 -75
- ivoryos/routes/control/templates/control/controllers_home.html +50 -50
- ivoryos/routes/control/templates/control/controllers_new.html +89 -89
- ivoryos/routes/database/database.py +188 -114
- ivoryos/routes/database/templates/database/experiment_database.html +72 -72
- ivoryos/routes/design/design.py +542 -406
- ivoryos/routes/design/templates/design/experiment_builder.html +415 -412
- ivoryos/routes/design/templates/design/experiment_run.html +325 -325
- ivoryos/routes/main/main.py +42 -25
- ivoryos/routes/main/templates/main/help.html +141 -141
- ivoryos/routes/main/templates/main/home.html +68 -68
- ivoryos/static/.DS_Store +0 -0
- ivoryos/static/js/overlay.js +12 -12
- ivoryos/static/js/socket_handler.js +34 -34
- ivoryos/static/js/sortable_card.js +24 -24
- ivoryos/static/js/sortable_design.js +36 -36
- ivoryos/static/style.css +201 -201
- ivoryos/templates/base.html +143 -143
- ivoryos/utils/db_models.py +518 -500
- ivoryos/utils/form.py +316 -316
- ivoryos/utils/global_config.py +67 -67
- ivoryos/utils/llm_agent.py +183 -183
- ivoryos/utils/script_runner.py +165 -164
- ivoryos/utils/utils.py +425 -422
- ivoryos/version.py +1 -0
- {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
- {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -166
- ivoryos-0.1.10.dist-info/RECORD +47 -0
- {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
- ivoryos-0.1.8.dist-info/RECORD +0 -45
- {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
ivoryos/routes/design/design.py
CHANGED
|
@@ -1,406 +1,542 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import pickle
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
from flask import Blueprint, redirect, url_for, flash, jsonify, send_file, request, render_template, session, \
|
|
8
|
-
current_app, g
|
|
9
|
-
from flask_login import login_required
|
|
10
|
-
from flask_socketio import SocketIO
|
|
11
|
-
from werkzeug.utils import secure_filename
|
|
12
|
-
|
|
13
|
-
from ivoryos.utils import utils
|
|
14
|
-
from ivoryos.utils.global_config import GlobalConfig
|
|
15
|
-
from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo
|
|
16
|
-
from ivoryos.utils.
|
|
17
|
-
from ivoryos.utils.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@design.route("/experiment/build
|
|
44
|
-
@
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
utils.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
deck
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
@
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1
|
+
import csv
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import pickle
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from flask import Blueprint, redirect, url_for, flash, jsonify, send_file, request, render_template, session, \
|
|
8
|
+
current_app, g
|
|
9
|
+
from flask_login import login_required
|
|
10
|
+
from flask_socketio import SocketIO
|
|
11
|
+
from werkzeug.utils import secure_filename
|
|
12
|
+
|
|
13
|
+
from ivoryos.utils import utils
|
|
14
|
+
from ivoryos.utils.global_config import GlobalConfig
|
|
15
|
+
from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo
|
|
16
|
+
from ivoryos.utils.db_models import Script
|
|
17
|
+
from ivoryos.utils.script_runner import ScriptRunner
|
|
18
|
+
|
|
19
|
+
socketio = SocketIO()
|
|
20
|
+
design = Blueprint('design', __name__, template_folder='templates/design')
|
|
21
|
+
|
|
22
|
+
global_config = GlobalConfig()
|
|
23
|
+
runner = ScriptRunner()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@socketio.on('abort_action')
|
|
27
|
+
def handle_abort_action():
|
|
28
|
+
runner.stop_execution()
|
|
29
|
+
socketio.emit('log', {'message': "aborted pending tasks"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@socketio.on('connect')
|
|
33
|
+
def handle_abort_action():
|
|
34
|
+
# Fetch log messages from local file
|
|
35
|
+
filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
|
|
36
|
+
with open(filename, 'r') as log_file:
|
|
37
|
+
log_history = log_file.readlines()
|
|
38
|
+
for message in log_history[-10:]:
|
|
39
|
+
socketio.emit('log', {'message': message})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@design.route("/experiment/build/", methods=['GET', 'POST'])
|
|
43
|
+
@design.route("/experiment/build/<instrument>/", methods=['GET', 'POST'])
|
|
44
|
+
@login_required
|
|
45
|
+
def experiment_builder(instrument=None):
|
|
46
|
+
"""
|
|
47
|
+
.. :quickref: Workflow Design; Build experiment workflow
|
|
48
|
+
|
|
49
|
+
**Experiment Builder**
|
|
50
|
+
|
|
51
|
+
This route allows users to build and edit experiment workflows. Users can interact with available instruments,
|
|
52
|
+
define variables, and manage experiment scripts.
|
|
53
|
+
|
|
54
|
+
.. http:get:: /experiment/build
|
|
55
|
+
|
|
56
|
+
Load the experiment builder interface.
|
|
57
|
+
|
|
58
|
+
:param instrument: The specific instrument for which to load functions and forms.
|
|
59
|
+
:type instrument: str
|
|
60
|
+
:status 200: Experiment builder loaded successfully.
|
|
61
|
+
|
|
62
|
+
.. http:post:: /experiment/build
|
|
63
|
+
|
|
64
|
+
Submit form data to add or modify actions in the experiment script.
|
|
65
|
+
|
|
66
|
+
**Adding action to canvas**
|
|
67
|
+
|
|
68
|
+
:form return: (optional) The name of the function or method to add to the script.
|
|
69
|
+
:form dynamic: depend on the selected instrument and its metadata.
|
|
70
|
+
|
|
71
|
+
:status 200: Action added or modified successfully.
|
|
72
|
+
:status 400: Validation errors in submitted form data.
|
|
73
|
+
:status 302: Toggles autofill or redirects to refresh the page.
|
|
74
|
+
|
|
75
|
+
**Toggle auto parameter name fill**:
|
|
76
|
+
|
|
77
|
+
:status 200: autofill toggled successfully
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
deck = global_config.deck
|
|
81
|
+
script = utils.get_script_file()
|
|
82
|
+
if deck and script.deck is None:
|
|
83
|
+
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
84
|
+
0] if deck.__name__ == "__main__" else deck.__name__
|
|
85
|
+
script.sort_actions()
|
|
86
|
+
|
|
87
|
+
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
88
|
+
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
89
|
+
off_line = current_app.config["OFF_LINE"]
|
|
90
|
+
enable_llm = current_app.config["ENABLE_LLM"]
|
|
91
|
+
autofill = session.get('autofill')
|
|
92
|
+
|
|
93
|
+
# autofill is not allowed for prep and cleanup
|
|
94
|
+
autofill = autofill if script.editing_type == "script" else False
|
|
95
|
+
forms = None
|
|
96
|
+
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
97
|
+
if off_line and pseudo_deck is None:
|
|
98
|
+
flash("Choose available deck below.")
|
|
99
|
+
|
|
100
|
+
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
101
|
+
|
|
102
|
+
functions = []
|
|
103
|
+
if deck:
|
|
104
|
+
deck_variables = global_config.deck_snapshot.keys()
|
|
105
|
+
else:
|
|
106
|
+
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
107
|
+
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
108
|
+
if instrument:
|
|
109
|
+
if instrument in ['if', 'while', 'variable', 'wait']:
|
|
110
|
+
forms = create_builtin_form(instrument)
|
|
111
|
+
else:
|
|
112
|
+
if deck:
|
|
113
|
+
function_metadata = global_config.deck_snapshot.get(instrument, {})
|
|
114
|
+
elif pseudo_deck:
|
|
115
|
+
function_metadata = pseudo_deck.get(instrument, {})
|
|
116
|
+
functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
|
|
117
|
+
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
118
|
+
if request.method == 'POST' and "hidden_name" in request.form:
|
|
119
|
+
all_kwargs = request.form.copy()
|
|
120
|
+
method_name = all_kwargs.pop("hidden_name", None)
|
|
121
|
+
# if method_name is not None:
|
|
122
|
+
form = forms.get(method_name)
|
|
123
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
124
|
+
|
|
125
|
+
if form and form.validate_on_submit():
|
|
126
|
+
function_name = kwargs.pop("hidden_name")
|
|
127
|
+
save_data = kwargs.pop('return', '')
|
|
128
|
+
variable_kwargs = {}
|
|
129
|
+
variable_kwargs_types = {}
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
variable_kwargs, variable_kwargs_types = utils.find_variable_in_script(script, kwargs)
|
|
133
|
+
|
|
134
|
+
for name in variable_kwargs.keys():
|
|
135
|
+
del kwargs[name]
|
|
136
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
137
|
+
|
|
138
|
+
except:
|
|
139
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
140
|
+
|
|
141
|
+
kwargs.update(variable_kwargs)
|
|
142
|
+
arg_types = {}
|
|
143
|
+
arg_types.update(variable_kwargs_types)
|
|
144
|
+
arg_types.update(primitive_arg_types)
|
|
145
|
+
all_kwargs.update(variable_kwargs)
|
|
146
|
+
|
|
147
|
+
action = {"instrument": instrument, "action": function_name,
|
|
148
|
+
"args": {name: arg for (name, arg) in kwargs.items()},
|
|
149
|
+
"return": save_data,
|
|
150
|
+
'arg_types': arg_types}
|
|
151
|
+
script.add_action(action=action)
|
|
152
|
+
else:
|
|
153
|
+
flash(form.errors)
|
|
154
|
+
|
|
155
|
+
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
156
|
+
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
157
|
+
if forms.validate_on_submit():
|
|
158
|
+
logic_type = kwargs.pop('builtin_name')
|
|
159
|
+
if 'variable' in kwargs:
|
|
160
|
+
script.add_variable(**kwargs)
|
|
161
|
+
else:
|
|
162
|
+
script.add_logic_action(logic_type=logic_type, **kwargs)
|
|
163
|
+
else:
|
|
164
|
+
flash(forms.errors)
|
|
165
|
+
|
|
166
|
+
# toggle autofill
|
|
167
|
+
elif request.method == 'POST' and "autofill" in request.form:
|
|
168
|
+
autofill = not autofill
|
|
169
|
+
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
170
|
+
session['autofill'] = autofill
|
|
171
|
+
utils.post_script_file(script)
|
|
172
|
+
design_buttons = [create_action_button(i) for i in script.currently_editing_script]
|
|
173
|
+
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
174
|
+
script=script, defined_variables=deck_variables,
|
|
175
|
+
local_variables=global_config.defined_variables,
|
|
176
|
+
functions=functions, forms=forms, buttons=design_buttons, format_name=format_name,
|
|
177
|
+
use_llm=enable_llm)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@design.route("/generate_code", methods=['POST'])
|
|
181
|
+
@login_required
|
|
182
|
+
def generate_code():
|
|
183
|
+
"""
|
|
184
|
+
.. :quickref: Text to Code; Generate code from user input and update the design canvas.
|
|
185
|
+
|
|
186
|
+
.. http:post:: /generate_code
|
|
187
|
+
|
|
188
|
+
:form prompt: user's prompt
|
|
189
|
+
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
190
|
+
:status 400: failed to initialize the AI agent redirects to :http:get:`/experiment/build`
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
agent = global_config.agent
|
|
194
|
+
enable_llm = current_app.config["ENABLE_LLM"]
|
|
195
|
+
instrument = request.form.get("instrument")
|
|
196
|
+
|
|
197
|
+
if request.method == 'POST' and "clear" in request.form:
|
|
198
|
+
session['prompt'][instrument] = ''
|
|
199
|
+
if request.method == 'POST' and "gen" in request.form:
|
|
200
|
+
prompt = request.form.get("prompt")
|
|
201
|
+
session['prompt'][instrument] = prompt
|
|
202
|
+
# sdl_module = utils.parse_functions(find_instrument_by_name(f'deck.{instrument}'), doc_string=True)
|
|
203
|
+
sdl_module = global_config.deck_snapshot.get(instrument, {})
|
|
204
|
+
empty_script = Script(author=session.get('user'))
|
|
205
|
+
if enable_llm and agent is None:
|
|
206
|
+
try:
|
|
207
|
+
model = current_app.config["LLM_MODEL"]
|
|
208
|
+
server = current_app.config["LLM_SERVER"]
|
|
209
|
+
module = current_app.config["MODULE"]
|
|
210
|
+
from ivoryos.utils.llm_agent import LlmAgent
|
|
211
|
+
agent = LlmAgent(host=server, model=model, output_path=os.path.dirname(os.path.abspath(module)))
|
|
212
|
+
except Exception as e:
|
|
213
|
+
flash(e.__str__())
|
|
214
|
+
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True)), 400
|
|
215
|
+
action_list = agent.generate_code(sdl_module, prompt)
|
|
216
|
+
for action in action_list:
|
|
217
|
+
action['instrument'] = instrument
|
|
218
|
+
action['return'] = ''
|
|
219
|
+
if "args" not in action:
|
|
220
|
+
action['args'] = {}
|
|
221
|
+
if "arg_types" not in action:
|
|
222
|
+
action['arg_types'] = {}
|
|
223
|
+
empty_script.add_action(action)
|
|
224
|
+
utils.post_script_file(empty_script)
|
|
225
|
+
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@design.route("/experiment", methods=['GET', 'POST'])
|
|
229
|
+
@login_required
|
|
230
|
+
def experiment_run():
|
|
231
|
+
"""
|
|
232
|
+
.. :quickref: Workflow Execution; Execute/iterate the workflow
|
|
233
|
+
|
|
234
|
+
.. http:get:: /experiment
|
|
235
|
+
|
|
236
|
+
Compile the workflow and load the experiment execution interface.
|
|
237
|
+
|
|
238
|
+
.. http:post:: /experiment
|
|
239
|
+
|
|
240
|
+
Start workflow execution
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
deck = global_config.deck
|
|
244
|
+
script = utils.get_script_file()
|
|
245
|
+
script.sort_actions()
|
|
246
|
+
off_line = current_app.config["OFF_LINE"]
|
|
247
|
+
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
248
|
+
# if not off_line and deck is None:
|
|
249
|
+
# # print("loading deck")
|
|
250
|
+
# module = current_app.config.get('MODULE', '')
|
|
251
|
+
# deck = sys.modules[module] if module else None
|
|
252
|
+
# script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
253
|
+
design_buttons = {stype: [create_action_button(i) for i in script.get_script(stype)] for stype in script.stypes}
|
|
254
|
+
config_preview = []
|
|
255
|
+
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
256
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
257
|
+
# print(exec_string)
|
|
258
|
+
config_file = request.args.get("filename")
|
|
259
|
+
config = []
|
|
260
|
+
if config_file:
|
|
261
|
+
session['config_file'] = config_file
|
|
262
|
+
filename = session.get("config_file")
|
|
263
|
+
if filename:
|
|
264
|
+
# config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
265
|
+
config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
266
|
+
config_preview = config[1:6]
|
|
267
|
+
arg_type = config.pop(0) # first entry is types
|
|
268
|
+
try:
|
|
269
|
+
exec(exec_string)
|
|
270
|
+
except Exception:
|
|
271
|
+
flash("Please check syntax!!")
|
|
272
|
+
return redirect(url_for("design.experiment_builder"))
|
|
273
|
+
# runner.globals_dict.update(globals())
|
|
274
|
+
run_name = script.name if script.name else "untitled"
|
|
275
|
+
|
|
276
|
+
dismiss = session.get("dismiss", None)
|
|
277
|
+
script = utils.get_script_file()
|
|
278
|
+
no_deck_warning = False
|
|
279
|
+
|
|
280
|
+
_, return_list = script.config_return()
|
|
281
|
+
config_list, config_type_list = script.config("script")
|
|
282
|
+
# config = script.config("script")
|
|
283
|
+
data_list = os.listdir(current_app.config['DATA_FOLDER'])
|
|
284
|
+
data_list.remove(".gitkeep") if ".gitkeep" in data_list else data_list
|
|
285
|
+
if deck is None:
|
|
286
|
+
no_deck_warning = True
|
|
287
|
+
flash(f"No deck is found, import {script.deck}")
|
|
288
|
+
elif script.deck:
|
|
289
|
+
is_deck_match = script.deck == deck.__name__ or script.deck == \
|
|
290
|
+
os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
291
|
+
if not is_deck_match:
|
|
292
|
+
flash(f"This script is not compatible with current deck, import {script.deck}")
|
|
293
|
+
if request.method == "POST":
|
|
294
|
+
bo_args = None
|
|
295
|
+
if "bo" in request.form:
|
|
296
|
+
bo_args = request.form.to_dict()
|
|
297
|
+
# ax_client = utils.ax_initiation(bo_args)
|
|
298
|
+
if "online-config" in request.form:
|
|
299
|
+
config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
|
|
300
|
+
repeat = request.form.get('repeat', None)
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
datapath = current_app.config["DATA_FOLDER"]
|
|
304
|
+
run_name = script.validate_function_name(run_name)
|
|
305
|
+
runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
|
|
306
|
+
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
307
|
+
output_path=datapath
|
|
308
|
+
)
|
|
309
|
+
except Exception as e:
|
|
310
|
+
flash(e)
|
|
311
|
+
return render_template('experiment_run.html', script=script.script_dict, filename=filename, dot_py=exec_string,
|
|
312
|
+
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
313
|
+
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
314
|
+
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons, history=deck_list)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@design.route("/toggle_script_type/<stype>")
|
|
318
|
+
@login_required
|
|
319
|
+
def toggle_script_type(stype=None):
|
|
320
|
+
"""
|
|
321
|
+
.. :quickref: Workflow Design; toggle the experimental phase for design canvas.
|
|
322
|
+
|
|
323
|
+
.. http:get:: /toggle_script_type
|
|
324
|
+
|
|
325
|
+
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
script = utils.get_script_file()
|
|
329
|
+
script.editing_type = stype
|
|
330
|
+
utils.post_script_file(script)
|
|
331
|
+
return redirect(url_for('design.experiment_builder'))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@design.route("/updateList", methods=['GET', 'POST'])
|
|
335
|
+
@login_required
|
|
336
|
+
def update_list():
|
|
337
|
+
order = request.form['order']
|
|
338
|
+
script = utils.get_script_file()
|
|
339
|
+
script.currently_editing_order = order.split(",", len(script.currently_editing_script))
|
|
340
|
+
utils.post_script_file(script)
|
|
341
|
+
return jsonify('Successfully Updated')
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# --------------------handle all the import/export and download/upload--------------------------
|
|
345
|
+
@design.route("/clear")
|
|
346
|
+
@login_required
|
|
347
|
+
def clear():
|
|
348
|
+
"""
|
|
349
|
+
.. :quickref: Workflow Design; clear the design canvas.
|
|
350
|
+
|
|
351
|
+
.. http:get:: /clear
|
|
352
|
+
|
|
353
|
+
:form prompt: user's prompt
|
|
354
|
+
:status 200: clear canvas and then redirects to :http:get:`/experiment/build`
|
|
355
|
+
"""
|
|
356
|
+
deck = global_config.deck
|
|
357
|
+
pseudo_name = session.get("pseudo_deck", "")
|
|
358
|
+
if deck:
|
|
359
|
+
deck_name = os.path.splitext(os.path.basename(deck.__file__))[
|
|
360
|
+
0] if deck.__name__ == "__main__" else deck.__name__
|
|
361
|
+
elif pseudo_name:
|
|
362
|
+
deck_name = pseudo_name
|
|
363
|
+
else:
|
|
364
|
+
deck_name = ''
|
|
365
|
+
script = Script(deck=deck_name, author=session.get('username'))
|
|
366
|
+
utils.post_script_file(script)
|
|
367
|
+
return redirect(url_for("design.experiment_builder"))
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@design.route("/import_pseudo", methods=['POST'])
|
|
371
|
+
@login_required
|
|
372
|
+
def import_pseudo():
|
|
373
|
+
"""
|
|
374
|
+
.. :quickref: Workflow Design; Import pseudo deck from deck history
|
|
375
|
+
|
|
376
|
+
.. http:post:: /import_pseudo
|
|
377
|
+
|
|
378
|
+
:form pkl_name: pseudo deck name
|
|
379
|
+
:status 302: load pseudo deck and then redirects to :http:get:`/experiment/build`
|
|
380
|
+
"""
|
|
381
|
+
pkl_name = request.form.get('pkl_name')
|
|
382
|
+
script = utils.get_script_file()
|
|
383
|
+
session['pseudo_deck'] = pkl_name
|
|
384
|
+
|
|
385
|
+
if script.deck is None or script.isEmpty():
|
|
386
|
+
script.deck = pkl_name.split('.')[0]
|
|
387
|
+
utils.post_script_file(script)
|
|
388
|
+
elif script.deck and not script.deck == pkl_name.split('.')[0]:
|
|
389
|
+
flash(f"Choose the deck with name {script.deck}")
|
|
390
|
+
return redirect(url_for("design.experiment_builder"))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@design.route('/uploads', methods=['POST'])
|
|
394
|
+
@login_required
|
|
395
|
+
def upload():
|
|
396
|
+
"""
|
|
397
|
+
.. :quickref: Workflow Execution; upload a workflow config file (.CSV)
|
|
398
|
+
|
|
399
|
+
.. http:post:: /uploads
|
|
400
|
+
|
|
401
|
+
:form file: workflow CSV config file
|
|
402
|
+
:status 302: save csv file and then redirects to :http:get:`/experiment`
|
|
403
|
+
"""
|
|
404
|
+
if request.method == "POST":
|
|
405
|
+
f = request.files['file']
|
|
406
|
+
if 'file' not in request.files:
|
|
407
|
+
flash('No file part')
|
|
408
|
+
if f.filename.split('.')[-1] == "csv":
|
|
409
|
+
filename = secure_filename(f.filename)
|
|
410
|
+
f.save(os.path.join(current_app.config['CSV_FOLDER'], filename))
|
|
411
|
+
session['config_file'] = filename
|
|
412
|
+
return redirect(url_for("design.experiment_run"))
|
|
413
|
+
else:
|
|
414
|
+
flash("Config file is in csv format")
|
|
415
|
+
return redirect(url_for("design.experiment_run"))
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@design.route('/download_results/<filename>')
|
|
419
|
+
@login_required
|
|
420
|
+
def download_results(filename):
|
|
421
|
+
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
422
|
+
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@design.route('/load_json', methods=['POST'])
|
|
426
|
+
@login_required
|
|
427
|
+
def load_json():
|
|
428
|
+
"""
|
|
429
|
+
.. :quickref: Workflow Design Ext; upload a workflow design file (.JSON)
|
|
430
|
+
|
|
431
|
+
.. http:post:: /load_json
|
|
432
|
+
|
|
433
|
+
:form file: workflow design JSON file
|
|
434
|
+
:status 302: load pseudo deck and then redirects to :http:get:`/experiment/build`
|
|
435
|
+
"""
|
|
436
|
+
if request.method == "POST":
|
|
437
|
+
f = request.files['file']
|
|
438
|
+
if 'file' not in request.files:
|
|
439
|
+
flash('No file part')
|
|
440
|
+
if f.filename.endswith("json"):
|
|
441
|
+
script_dict = json.load(f)
|
|
442
|
+
utils.post_script_file(script_dict, is_dict=True)
|
|
443
|
+
else:
|
|
444
|
+
flash("Script file need to be JSON file")
|
|
445
|
+
return redirect(url_for("design.experiment_builder"))
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@design.route('/download/<filetype>')
|
|
449
|
+
@login_required
|
|
450
|
+
def download(filetype):
|
|
451
|
+
script = utils.get_script_file()
|
|
452
|
+
run_name = script.name if script.name else "untitled"
|
|
453
|
+
if filetype == "configure":
|
|
454
|
+
filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}_config.csv")
|
|
455
|
+
with open(filepath, 'w', newline='') as f:
|
|
456
|
+
writer = csv.writer(f)
|
|
457
|
+
cfg, cfg_types = script.config("script")
|
|
458
|
+
writer.writerow(cfg)
|
|
459
|
+
writer.writerow(list(cfg_types.values()))
|
|
460
|
+
elif filetype == "script":
|
|
461
|
+
script.sort_actions()
|
|
462
|
+
json_object = json.dumps(script.as_dict())
|
|
463
|
+
filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}.json")
|
|
464
|
+
with open(filepath, "w") as outfile:
|
|
465
|
+
outfile.write(json_object)
|
|
466
|
+
elif filetype == "python":
|
|
467
|
+
filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
|
|
468
|
+
|
|
469
|
+
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@design.route("/edit/<uuid>", methods=['GET', 'POST'])
|
|
473
|
+
@login_required
|
|
474
|
+
def edit_action(uuid: str):
|
|
475
|
+
"""
|
|
476
|
+
.. :quickref: Workflow Design; edit parameters of an action step on canvas
|
|
477
|
+
|
|
478
|
+
.. http:get:: /edit
|
|
479
|
+
|
|
480
|
+
Load parameter form of an action step
|
|
481
|
+
|
|
482
|
+
.. http:post:: /edit
|
|
483
|
+
|
|
484
|
+
:param uuid: The step's uuid
|
|
485
|
+
:type uuid: str
|
|
486
|
+
|
|
487
|
+
:form dynamic form: workflow step dynamic inputs
|
|
488
|
+
:status 302: save changes and then redirects to :http:get:`/experiment/build`
|
|
489
|
+
"""
|
|
490
|
+
script = utils.get_script_file()
|
|
491
|
+
action = script.find_by_uuid(uuid)
|
|
492
|
+
session['edit_action'] = action
|
|
493
|
+
if request.method == "POST":
|
|
494
|
+
if "back" not in request.form:
|
|
495
|
+
args = request.form.to_dict()
|
|
496
|
+
save_as = args.pop('return', '')
|
|
497
|
+
try:
|
|
498
|
+
script.update_by_uuid(uuid=uuid, args=args, output=save_as)
|
|
499
|
+
except Exception as e:
|
|
500
|
+
flash(e.__str__())
|
|
501
|
+
session.pop('edit_action')
|
|
502
|
+
return redirect(url_for('design.experiment_builder'))
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@design.route("/delete/<id>")
|
|
506
|
+
@login_required
|
|
507
|
+
def delete_action(id: int):
|
|
508
|
+
"""
|
|
509
|
+
.. :quickref: Workflow Design; delete an action step on canvas
|
|
510
|
+
|
|
511
|
+
.. http:get:: /delete
|
|
512
|
+
|
|
513
|
+
:param id: The step number id
|
|
514
|
+
:type id: int
|
|
515
|
+
|
|
516
|
+
:status 302: save changes and then redirects to :http:get:`/experiment/build`
|
|
517
|
+
"""
|
|
518
|
+
back = request.referrer
|
|
519
|
+
script = utils.get_script_file()
|
|
520
|
+
script.delete_action(id)
|
|
521
|
+
utils.post_script_file(script)
|
|
522
|
+
return redirect(back)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@design.route("/duplicate/<id>")
|
|
526
|
+
@login_required
|
|
527
|
+
def duplicate_action(id: int):
|
|
528
|
+
"""
|
|
529
|
+
.. :quickref: Workflow Design; duplicate an action step on canvas
|
|
530
|
+
|
|
531
|
+
.. http:get:: /duplicate
|
|
532
|
+
|
|
533
|
+
:param id: The step number id
|
|
534
|
+
:type id: int
|
|
535
|
+
|
|
536
|
+
:status 302: save changes and then redirects to :http:get:`/experiment/build`
|
|
537
|
+
"""
|
|
538
|
+
back = request.referrer
|
|
539
|
+
script = utils.get_script_file()
|
|
540
|
+
script.duplicate_action(id)
|
|
541
|
+
utils.post_script_file(script)
|
|
542
|
+
return redirect(back)
|