ivoryos 0.1.9__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.

Files changed (37) hide show
  1. ivoryos/__init__.py +118 -99
  2. ivoryos/config.py +47 -47
  3. ivoryos/routes/auth/auth.py +100 -65
  4. ivoryos/routes/auth/templates/auth/login.html +25 -25
  5. ivoryos/routes/auth/templates/auth/signup.html +32 -32
  6. ivoryos/routes/control/control.py +400 -272
  7. ivoryos/routes/control/templates/control/controllers.html +75 -75
  8. ivoryos/routes/control/templates/control/controllers_home.html +50 -50
  9. ivoryos/routes/control/templates/control/controllers_new.html +89 -89
  10. ivoryos/routes/database/database.py +188 -114
  11. ivoryos/routes/database/templates/database/experiment_database.html +72 -72
  12. ivoryos/routes/design/design.py +541 -416
  13. ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
  14. ivoryos/routes/design/templates/design/experiment_run.html +325 -325
  15. ivoryos/routes/main/main.py +42 -25
  16. ivoryos/routes/main/templates/main/help.html +141 -141
  17. ivoryos/routes/main/templates/main/home.html +68 -68
  18. ivoryos/static/.DS_Store +0 -0
  19. ivoryos/static/js/overlay.js +12 -12
  20. ivoryos/static/js/socket_handler.js +34 -34
  21. ivoryos/static/js/sortable_card.js +24 -24
  22. ivoryos/static/js/sortable_design.js +36 -36
  23. ivoryos/static/style.css +201 -201
  24. ivoryos/templates/base.html +143 -143
  25. ivoryos/utils/db_models.py +518 -518
  26. ivoryos/utils/form.py +316 -316
  27. ivoryos/utils/global_config.py +67 -67
  28. ivoryos/utils/llm_agent.py +183 -183
  29. ivoryos/utils/script_runner.py +165 -164
  30. ivoryos/utils/utils.py +425 -422
  31. ivoryos/version.py +1 -0
  32. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
  33. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -169
  34. ivoryos-0.1.10.dist-info/RECORD +47 -0
  35. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
  36. ivoryos-0.1.9.dist-info/RECORD +0 -45
  37. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
ivoryos/utils/form.py CHANGED
@@ -1,316 +1,316 @@
1
- from wtforms.fields.core import Field
2
- from wtforms.validators import InputRequired
3
- from wtforms.widgets.core import TextInput
4
-
5
- from flask_wtf import FlaskForm
6
- from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
7
- import inspect
8
-
9
-
10
- def find_variable(data, script):
11
- # TODO: needs to check for valid order of variables, important when editting
12
- added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
13
- action["instrument"] == "variable"
14
- # or action["return"] # TODO find returns
15
- ]
16
- for added_variable in added_variables:
17
- if added_variable["action"] == data:
18
- return data, added_variable["args"]
19
- # if added_variable["return"] == data:
20
- # return data, None
21
- return None, None
22
-
23
-
24
- class VariableOrStringField(Field):
25
- widget = TextInput()
26
-
27
- def __init__(self, label='', validators=None, script=None, **kwargs):
28
- super(VariableOrStringField, self).__init__(label, validators, **kwargs)
29
- self.script = script
30
-
31
- def process_formdata(self, valuelist):
32
- if valuelist:
33
- if not self.script.editing_type == "script" and valuelist[0].startswith("#"):
34
- raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
35
- self.data = valuelist[0]
36
-
37
- def _value(self):
38
- if self.script:
39
- variable, value = find_variable(self.data, self.script)
40
- if variable:
41
- return variable
42
-
43
- return str(self.data) if self.data is not None else ""
44
-
45
-
46
- class VariableOrFloatField(Field):
47
- widget = TextInput()
48
-
49
- def __init__(self, label='', validators=None, script=None, **kwargs):
50
- super(VariableOrFloatField, self).__init__(label, validators, **kwargs)
51
- self.script = script
52
-
53
- def _value(self):
54
- if self.script:
55
- variable, value = find_variable(self.data, self.script)
56
- if variable:
57
- return variable
58
-
59
- if self.raw_data:
60
- return self.raw_data[0]
61
- if self.data is not None:
62
- return str(self.data)
63
- return ""
64
-
65
- def process_formdata(self, valuelist):
66
- if not valuelist:
67
- return
68
- elif valuelist[0].startswith("#"):
69
- if not self.script.editing_type == "script":
70
- raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
71
- self.data = valuelist[0]
72
- return
73
- try:
74
- if self.script:
75
- try:
76
- variable, value = find_variable(valuelist[0], self.script)
77
- if variable:
78
- float(value)
79
- self.data = str(variable)
80
- return
81
- except ValueError:
82
- pass
83
-
84
- self.data = float(valuelist[0])
85
- except ValueError as exc:
86
- self.data = None
87
- raise ValueError(self.gettext("Not a valid float value.")) from exc
88
-
89
-
90
- # unset_value = UnsetValue()
91
-
92
-
93
- class VariableOrIntField(Field):
94
- widget = TextInput()
95
-
96
- def __init__(self, label='', validators=None, script=None, **kwargs):
97
- super(VariableOrIntField, self).__init__(label, validators, **kwargs)
98
- self.script = script
99
-
100
- def _value(self):
101
- if self.script:
102
- variable, value = find_variable(self.data, self.script)
103
- if variable:
104
- return variable
105
-
106
- if self.raw_data:
107
- return self.raw_data[0]
108
- if self.data is not None:
109
- return str(self.data)
110
- return ""
111
-
112
- # def process_data(self, value):
113
- #
114
- # if self.script:
115
- # variable, var_value = find_variable(value, self.script)
116
- # if variable:
117
- # try:
118
- # int(var_value)
119
- # self.data = str(variable)
120
- # return
121
- # except ValueError:
122
- # pass
123
- # if value is None or value is unset_value:
124
- # self.data = None
125
- # return
126
- # try:
127
- # self.data = int(value)
128
- # except (ValueError, TypeError) as exc:
129
- # self.data = None
130
- # raise ValueError(self.gettext("Not a valid integer value.")) from exc
131
-
132
- def process_formdata(self, valuelist):
133
- if not valuelist:
134
- return
135
- if self.script:
136
- variable, var_value = find_variable(valuelist[0], self.script)
137
- if variable:
138
- try:
139
- int(var_value)
140
- self.data = str(variable)
141
- return
142
- except ValueError:
143
- pass
144
- if valuelist[0].startswith("#"):
145
- if not self.script.editing_type == "script":
146
- raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
147
- self.data = valuelist[0]
148
- return
149
- try:
150
- self.data = int(valuelist[0])
151
- except ValueError as exc:
152
- self.data = None
153
- raise ValueError(self.gettext("Not a valid integer value.")) from exc
154
-
155
-
156
- class VariableOrBoolField(BooleanField):
157
- widget = TextInput()
158
-
159
- def __init__(self, label='', validators=None, script=None, **kwargs):
160
- super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
161
- self.script = script
162
-
163
- def process_data(self, value):
164
-
165
- if self.script:
166
- variable, var_value = find_variable(value, self.script)
167
- if variable:
168
- try:
169
- bool(var_value)
170
- return variable
171
- except ValueError:
172
- return
173
-
174
- self.data = bool(value)
175
-
176
- def process_formdata(self, valuelist):
177
- if not valuelist or type(valuelist) is list and valuelist[0] == '':
178
- self.data = False
179
- elif valuelist and valuelist[0].startswith("#"):
180
- if not self.script.editing_type == "script":
181
- raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
182
- self.data = valuelist[0]
183
- else:
184
- self.data = True
185
-
186
- def _value(self):
187
-
188
- if self.script:
189
- variable, value = find_variable(self.raw_data, self.script)
190
- if variable:
191
- return variable
192
-
193
- if self.raw_data:
194
- return str(self.raw_data[0])
195
- return "y"
196
-
197
-
198
- def format_name(name):
199
- """Converts 'example_name' to 'Example Name'."""
200
- name = name.split(".")[-1]
201
- text = ' '.join(word for word in name.split('_'))
202
- return text.capitalize()
203
-
204
-
205
- def create_form_for_method(method, method_name, autofill, script=None, design=True):
206
- class DynamicForm(FlaskForm):
207
- pass
208
-
209
- annotation_mapping = {
210
- int: (VariableOrIntField if design else IntegerField, 'Enter integer value'),
211
- float: (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
212
- str: (VariableOrStringField if design else StringField, 'Enter text'),
213
- bool: (VariableOrBoolField if design else BooleanField, 'Empty for false')
214
- }
215
- sig = method if type(method) is inspect.Signature else inspect.signature(method)
216
-
217
- for param in sig.parameters.values():
218
- if param.name == 'self':
219
- continue
220
- formatted_param_name = format_name(param.name)
221
- field_kwargs = {
222
- "label": formatted_param_name,
223
- "default": f'#{param.name}' if autofill else (param.default if param.default is not param.empty else None),
224
- "validators": [InputRequired()] if param.default is param.empty else None,
225
- **({"script": script} if (autofill or design) else {})
226
- }
227
- field_class, placeholder_text = annotation_mapping.get(
228
- param.annotation,
229
- (VariableOrStringField if design else StringField, f'Enter {param.annotation} value')
230
- )
231
- render_kwargs = {"placeholder": placeholder_text}
232
-
233
- # Create the field with additional rendering kwargs for placeholder text
234
- field = field_class(**field_kwargs, render_kw=render_kwargs)
235
- setattr(DynamicForm, param.name, field)
236
-
237
- # setattr(DynamicForm, f'add', fname)
238
- return DynamicForm
239
-
240
-
241
- # Create forms for each method in DummySDLDeck
242
- def create_add_form(attr, attr_name, autofill, script=None, design=True):
243
- dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
244
- if design:
245
- return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
246
- setattr(dynamic_form, 'return', return_value)
247
- hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
248
- setattr(dynamic_form, 'hidden_name', hidden_method_name)
249
- return dynamic_form
250
-
251
-
252
- def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
253
- # sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
254
- method_forms = {}
255
- for attr_name in dir(sdl_module):
256
- attr = getattr(sdl_module, attr_name)
257
- if inspect.ismethod(attr) and not attr_name.startswith('_'):
258
- form_class = create_add_form(attr, attr_name, autofill, script, design)
259
- method_forms[attr_name] = form_class()
260
- return method_forms
261
-
262
-
263
- def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
264
- '''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
265
- method_forms = {}
266
- for attr_name, signature in pseudo.items():
267
- form_class = create_add_form(signature, attr_name, autofill, script, design)
268
- method_forms[attr_name] = form_class()
269
- return method_forms
270
-
271
-
272
- def create_builtin_form(logic_type):
273
- class BuiltinFunctionForm(FlaskForm):
274
- pass
275
-
276
- placeholder_text = f'Enter numbers' if logic_type == 'wait' else f'Enter statement'
277
- description_text = f'Your variable can be numbers, boolean (True or False) or text ("text")' if logic_type == 'variable' else ''
278
- field_class = FloatField if logic_type == 'wait' else StringField # Default to StringField as a fallback
279
- field_kwargs = {
280
- "label": f'statement',
281
- "validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
282
- "description": description_text,
283
- }
284
- render_kwargs = {"placeholder": placeholder_text}
285
- field = field_class(**field_kwargs, render_kw=render_kwargs)
286
- setattr(BuiltinFunctionForm, "statement", field)
287
- if logic_type == 'variable':
288
- variable_field = StringField(label=f'variable', validators=[InputRequired()],
289
- description="Your variable name cannot include space",
290
- render_kw=render_kwargs)
291
- setattr(BuiltinFunctionForm, "variable", variable_field)
292
- hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
293
- setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
294
- return BuiltinFunctionForm()
295
-
296
-
297
- def create_action_button(s: dict):
298
- style = ""
299
- if s['instrument'] in ['if', 'while']:
300
- text = f"{s['action']} {s['args']}"
301
- style = "background-color: tomato"
302
- elif s['instrument'] == 'variable':
303
- text = f"{s['action']} = {s['args']}"
304
- else:
305
- # regular action button
306
- prefix = f"{s['return']} = " if s['return'] else ""
307
- action_text = f"{s['instrument'].split('.')[-1] if s['instrument'].startswith('deck') else s['instrument']}.{s['action']}"
308
- arg_string = ""
309
- if s['args']:
310
- if type(s['args']) is dict:
311
- arg_string = "(" + ", ".join([f"{k} = {v}" for k, v in s['args'].items()]) + ")"
312
- else:
313
- arg_string = f"= {s['args']}"
314
-
315
- text = f"{prefix}{action_text} {arg_string}"
316
- return dict(label=text, style=style, uuid=s["uuid"], id=s["id"], instrument=s['instrument'])
1
+ from wtforms.fields.core import Field
2
+ from wtforms.validators import InputRequired
3
+ from wtforms.widgets.core import TextInput
4
+
5
+ from flask_wtf import FlaskForm
6
+ from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
7
+ import inspect
8
+
9
+
10
+ def find_variable(data, script):
11
+ # TODO: needs to check for valid order of variables, important when editting
12
+ added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
13
+ action["instrument"] == "variable"
14
+ # or action["return"] # TODO find returns
15
+ ]
16
+ for added_variable in added_variables:
17
+ if added_variable["action"] == data:
18
+ return data, added_variable["args"]
19
+ # if added_variable["return"] == data:
20
+ # return data, None
21
+ return None, None
22
+
23
+
24
+ class VariableOrStringField(Field):
25
+ widget = TextInput()
26
+
27
+ def __init__(self, label='', validators=None, script=None, **kwargs):
28
+ super(VariableOrStringField, self).__init__(label, validators, **kwargs)
29
+ self.script = script
30
+
31
+ def process_formdata(self, valuelist):
32
+ if valuelist:
33
+ if not self.script.editing_type == "script" and valuelist[0].startswith("#"):
34
+ raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
35
+ self.data = valuelist[0]
36
+
37
+ def _value(self):
38
+ if self.script:
39
+ variable, value = find_variable(self.data, self.script)
40
+ if variable:
41
+ return variable
42
+
43
+ return str(self.data) if self.data is not None else ""
44
+
45
+
46
+ class VariableOrFloatField(Field):
47
+ widget = TextInput()
48
+
49
+ def __init__(self, label='', validators=None, script=None, **kwargs):
50
+ super(VariableOrFloatField, self).__init__(label, validators, **kwargs)
51
+ self.script = script
52
+
53
+ def _value(self):
54
+ if self.script:
55
+ variable, value = find_variable(self.data, self.script)
56
+ if variable:
57
+ return variable
58
+
59
+ if self.raw_data:
60
+ return self.raw_data[0]
61
+ if self.data is not None:
62
+ return str(self.data)
63
+ return ""
64
+
65
+ def process_formdata(self, valuelist):
66
+ if not valuelist:
67
+ return
68
+ elif valuelist[0].startswith("#"):
69
+ if not self.script.editing_type == "script":
70
+ raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
71
+ self.data = valuelist[0]
72
+ return
73
+ try:
74
+ if self.script:
75
+ try:
76
+ variable, value = find_variable(valuelist[0], self.script)
77
+ if variable:
78
+ float(value)
79
+ self.data = str(variable)
80
+ return
81
+ except ValueError:
82
+ pass
83
+
84
+ self.data = float(valuelist[0])
85
+ except ValueError as exc:
86
+ self.data = None
87
+ raise ValueError(self.gettext("Not a valid float value.")) from exc
88
+
89
+
90
+ # unset_value = UnsetValue()
91
+
92
+
93
+ class VariableOrIntField(Field):
94
+ widget = TextInput()
95
+
96
+ def __init__(self, label='', validators=None, script=None, **kwargs):
97
+ super(VariableOrIntField, self).__init__(label, validators, **kwargs)
98
+ self.script = script
99
+
100
+ def _value(self):
101
+ if self.script:
102
+ variable, value = find_variable(self.data, self.script)
103
+ if variable:
104
+ return variable
105
+
106
+ if self.raw_data:
107
+ return self.raw_data[0]
108
+ if self.data is not None:
109
+ return str(self.data)
110
+ return ""
111
+
112
+ # def process_data(self, value):
113
+ #
114
+ # if self.script:
115
+ # variable, var_value = find_variable(value, self.script)
116
+ # if variable:
117
+ # try:
118
+ # int(var_value)
119
+ # self.data = str(variable)
120
+ # return
121
+ # except ValueError:
122
+ # pass
123
+ # if value is None or value is unset_value:
124
+ # self.data = None
125
+ # return
126
+ # try:
127
+ # self.data = int(value)
128
+ # except (ValueError, TypeError) as exc:
129
+ # self.data = None
130
+ # raise ValueError(self.gettext("Not a valid integer value.")) from exc
131
+
132
+ def process_formdata(self, valuelist):
133
+ if not valuelist:
134
+ return
135
+ if self.script:
136
+ variable, var_value = find_variable(valuelist[0], self.script)
137
+ if variable:
138
+ try:
139
+ int(var_value)
140
+ self.data = str(variable)
141
+ return
142
+ except ValueError:
143
+ pass
144
+ if valuelist[0].startswith("#"):
145
+ if not self.script.editing_type == "script":
146
+ raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
147
+ self.data = valuelist[0]
148
+ return
149
+ try:
150
+ self.data = int(valuelist[0])
151
+ except ValueError as exc:
152
+ self.data = None
153
+ raise ValueError(self.gettext("Not a valid integer value.")) from exc
154
+
155
+
156
+ class VariableOrBoolField(BooleanField):
157
+ widget = TextInput()
158
+
159
+ def __init__(self, label='', validators=None, script=None, **kwargs):
160
+ super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
161
+ self.script = script
162
+
163
+ def process_data(self, value):
164
+
165
+ if self.script:
166
+ variable, var_value = find_variable(value, self.script)
167
+ if variable:
168
+ try:
169
+ bool(var_value)
170
+ return variable
171
+ except ValueError:
172
+ return
173
+
174
+ self.data = bool(value)
175
+
176
+ def process_formdata(self, valuelist):
177
+ if not valuelist or type(valuelist) is list and valuelist[0] == '':
178
+ self.data = False
179
+ elif valuelist and valuelist[0].startswith("#"):
180
+ if not self.script.editing_type == "script":
181
+ raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
182
+ self.data = valuelist[0]
183
+ else:
184
+ self.data = True
185
+
186
+ def _value(self):
187
+
188
+ if self.script:
189
+ variable, value = find_variable(self.raw_data, self.script)
190
+ if variable:
191
+ return variable
192
+
193
+ if self.raw_data:
194
+ return str(self.raw_data[0])
195
+ return "y"
196
+
197
+
198
+ def format_name(name):
199
+ """Converts 'example_name' to 'Example Name'."""
200
+ name = name.split(".")[-1]
201
+ text = ' '.join(word for word in name.split('_'))
202
+ return text.capitalize()
203
+
204
+
205
+ def create_form_for_method(method, method_name, autofill, script=None, design=True):
206
+ class DynamicForm(FlaskForm):
207
+ pass
208
+
209
+ annotation_mapping = {
210
+ int: (VariableOrIntField if design else IntegerField, 'Enter integer value'),
211
+ float: (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
212
+ str: (VariableOrStringField if design else StringField, 'Enter text'),
213
+ bool: (VariableOrBoolField if design else BooleanField, 'Empty for false')
214
+ }
215
+ sig = method if type(method) is inspect.Signature else inspect.signature(method)
216
+
217
+ for param in sig.parameters.values():
218
+ if param.name == 'self':
219
+ continue
220
+ formatted_param_name = format_name(param.name)
221
+ field_kwargs = {
222
+ "label": formatted_param_name,
223
+ "default": f'#{param.name}' if autofill else (param.default if param.default is not param.empty else None),
224
+ "validators": [InputRequired()] if param.default is param.empty else None,
225
+ **({"script": script} if (autofill or design) else {})
226
+ }
227
+ field_class, placeholder_text = annotation_mapping.get(
228
+ param.annotation,
229
+ (VariableOrStringField if design else StringField, f'Enter {param.annotation} value')
230
+ )
231
+ render_kwargs = {"placeholder": placeholder_text}
232
+
233
+ # Create the field with additional rendering kwargs for placeholder text
234
+ field = field_class(**field_kwargs, render_kw=render_kwargs)
235
+ setattr(DynamicForm, param.name, field)
236
+
237
+ # setattr(DynamicForm, f'add', fname)
238
+ return DynamicForm
239
+
240
+
241
+ # Create forms for each method in DummySDLDeck
242
+ def create_add_form(attr, attr_name, autofill, script=None, design=True):
243
+ dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
244
+ if design:
245
+ return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
246
+ setattr(dynamic_form, 'return', return_value)
247
+ hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
248
+ setattr(dynamic_form, 'hidden_name', hidden_method_name)
249
+ return dynamic_form
250
+
251
+
252
+ def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
253
+ # sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
254
+ method_forms = {}
255
+ for attr_name in dir(sdl_module):
256
+ attr = getattr(sdl_module, attr_name)
257
+ if inspect.ismethod(attr) and not attr_name.startswith('_'):
258
+ form_class = create_add_form(attr, attr_name, autofill, script, design)
259
+ method_forms[attr_name] = form_class()
260
+ return method_forms
261
+
262
+
263
+ def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
264
+ '''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
265
+ method_forms = {}
266
+ for attr_name, signature in pseudo.items():
267
+ form_class = create_add_form(signature, attr_name, autofill, script, design)
268
+ method_forms[attr_name] = form_class()
269
+ return method_forms
270
+
271
+
272
+ def create_builtin_form(logic_type):
273
+ class BuiltinFunctionForm(FlaskForm):
274
+ pass
275
+
276
+ placeholder_text = f'Enter numbers' if logic_type == 'wait' else f'Enter statement'
277
+ description_text = f'Your variable can be numbers, boolean (True or False) or text ("text")' if logic_type == 'variable' else ''
278
+ field_class = FloatField if logic_type == 'wait' else StringField # Default to StringField as a fallback
279
+ field_kwargs = {
280
+ "label": f'statement',
281
+ "validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
282
+ "description": description_text,
283
+ }
284
+ render_kwargs = {"placeholder": placeholder_text}
285
+ field = field_class(**field_kwargs, render_kw=render_kwargs)
286
+ setattr(BuiltinFunctionForm, "statement", field)
287
+ if logic_type == 'variable':
288
+ variable_field = StringField(label=f'variable', validators=[InputRequired()],
289
+ description="Your variable name cannot include space",
290
+ render_kw=render_kwargs)
291
+ setattr(BuiltinFunctionForm, "variable", variable_field)
292
+ hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
293
+ setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
294
+ return BuiltinFunctionForm()
295
+
296
+
297
+ def create_action_button(s: dict):
298
+ style = ""
299
+ if s['instrument'] in ['if', 'while']:
300
+ text = f"{s['action']} {s['args']}"
301
+ style = "background-color: tomato"
302
+ elif s['instrument'] == 'variable':
303
+ text = f"{s['action']} = {s['args']}"
304
+ else:
305
+ # regular action button
306
+ prefix = f"{s['return']} = " if s['return'] else ""
307
+ action_text = f"{s['instrument'].split('.')[-1] if s['instrument'].startswith('deck') else s['instrument']}.{s['action']}"
308
+ arg_string = ""
309
+ if s['args']:
310
+ if type(s['args']) is dict:
311
+ arg_string = "(" + ", ".join([f"{k} = {v}" for k, v in s['args'].items()]) + ")"
312
+ else:
313
+ arg_string = f"= {s['args']}"
314
+
315
+ text = f"{prefix}{action_text} {arg_string}"
316
+ return dict(label=text, style=style, uuid=s["uuid"], id=s["id"], instrument=s['instrument'])