ivoryos 1.2.7__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

@@ -6,14 +6,20 @@ import time
6
6
  from datetime import datetime
7
7
 
8
8
  from ivoryos.utils import utils, bo_campaign
9
- from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep, db, SingleStep
9
+ from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep, db, WorkflowPhase
10
10
  from ivoryos.utils.global_config import GlobalConfig
11
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
11
12
 
12
13
  global_config = GlobalConfig()
13
14
  global deck
14
15
  deck = None
15
16
  # global deck, registered_workflows
16
17
  # deck, registered_workflows = None, None
18
+ class HumanInterventionRequired(Exception):
19
+ pass
20
+
21
+ def pause(reason="Human intervention required"):
22
+ raise HumanInterventionRequired(reason)
17
23
 
18
24
  class ScriptRunner:
19
25
  def __init__(self, globals_dict=None):
@@ -87,7 +93,7 @@ class ScriptRunner:
87
93
  thread.start()
88
94
  return thread
89
95
 
90
- def exec_steps(self, script, section_name, logger, socketio, run_id, i_progress, **kwargs):
96
+ def exec_steps(self, script, section_name, logger, socketio, phase_id, **kwargs):
91
97
  """
92
98
  Executes a function defined in a string line by line
93
99
  :param func_str: The function as a string
@@ -110,9 +116,15 @@ class ScriptRunner:
110
116
  # Parse function body from string
111
117
  temp_connections = global_config.defined_variables
112
118
  # Prepare execution environment
113
- exec_globals = {"deck": deck, "time":time} # Add required global objects
119
+ exec_globals = {"deck": deck, "time":time, "pause": pause} # Add required global objects
114
120
  # exec_globals = {"deck": deck, "time": time, "registered_workflows":registered_workflows} # Add required global objects
115
121
  exec_globals.update(temp_connections)
122
+
123
+ # Inject all block categories
124
+ for category, data in BUILDING_BLOCKS.items():
125
+ for method_name, method in data.items():
126
+ exec_globals[method_name] = method["func"]
127
+
116
128
  exec_locals = {} # Local execution scope
117
129
 
118
130
  # Define function arguments manually in exec_locals
@@ -124,9 +136,9 @@ class ScriptRunner:
124
136
  if self.stop_current_event.is_set():
125
137
  logger.info(f'Stopping execution during {section_name}')
126
138
  step = WorkflowStep(
127
- workflow_id=run_id,
128
- phase=section_name,
129
- repeat_index=i_progress,
139
+ phase_id=phase_id,
140
+ # phase=section_name,
141
+ # repeat_index=i_progress,
130
142
  step_index=index,
131
143
  method_name="stop",
132
144
  start_time=datetime.now(),
@@ -136,21 +148,24 @@ class ScriptRunner:
136
148
  db.session.add(step)
137
149
  break
138
150
  line = step_list[index]
139
- method_name = line.strip().split("(")[0] if "(" in line else line.strip()
140
- start_time = datetime.now()
151
+
152
+ method_name = line.strip()
153
+ # start_time = datetime.now()
154
+
141
155
  step = WorkflowStep(
142
- workflow_id=run_id,
143
- phase=section_name,
144
- repeat_index=i_progress,
156
+ phase_id=phase_id,
157
+ # phase=section_name,
158
+ # repeat_index=i_progress,
145
159
  step_index=index,
146
160
  method_name=method_name,
147
- start_time=start_time,
161
+ start_time=datetime.now(),
148
162
  )
149
163
  db.session.add(step)
150
164
  db.session.commit()
165
+
151
166
  logger.info(f"Executing: {line}")
152
167
  socketio.emit('execution', {'section': f"{section_name}-{index}"})
153
- # self._emit_progress(socketio, 100)
168
+
154
169
  # if line.startswith("registered_workflows"):
155
170
  # line = line.replace("registered_workflows.", "")
156
171
  try:
@@ -160,7 +175,15 @@ class ScriptRunner:
160
175
  self.safe_sleep(duration)
161
176
  else:
162
177
  exec(line, exec_globals, exec_locals)
163
- step.run_error = False
178
+ # step.run_error = False
179
+
180
+ except HumanInterventionRequired as e:
181
+ logger.warning(f"Human intervention required: {e}")
182
+ socketio.emit('human_intervention', {'message': str(e)})
183
+ # Instead of auto-resume, explicitly stay paused until user action
184
+ # step.run_error = False
185
+ self.toggle_pause()
186
+
164
187
  except Exception as e:
165
188
  logger.error(f"Error during script execution: {e}")
166
189
  socketio.emit('error', {'message': str(e)})
@@ -168,7 +191,7 @@ class ScriptRunner:
168
191
  step.run_error = True
169
192
  self.toggle_pause()
170
193
  step.end_time = datetime.now()
171
- # db.session.add(step)
194
+ step.output = exec_locals
172
195
  db.session.commit()
173
196
 
174
197
  self.pause_event.wait()
@@ -176,10 +199,9 @@ class ScriptRunner:
176
199
  # todo update script during the run
177
200
  # _func_str = script.compile()
178
201
  # step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
179
- if not step.run_error:
180
- index += 1
181
- elif not self.retry:
202
+ if not step.run_error or not self.retry:
182
203
  index += 1
204
+
183
205
  return exec_locals # Return the 'results' variable
184
206
 
185
207
  def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
@@ -191,14 +213,17 @@ class ScriptRunner:
191
213
  filename = None
192
214
  error_flag = False
193
215
  # create a new run entry in the database
194
- try:
195
- with current_app.app_context():
196
- run = WorkflowRun(name=script.name or "untitled", platform=script.deck or "deck",start_time=datetime.now())
197
- db.session.add(run)
198
- db.session.commit()
199
- run_id = run.id # Save the ID
200
- global_config.runner_status = {"id":run_id, "type": "workflow"}
216
+ repeat_mode = "batch" if config else "optimizer" if bo_args or optimizer else "repeat"
217
+ with current_app.app_context():
218
+ run = WorkflowRun(name=script.name or "untitled", platform=script.deck or "deck", start_time=datetime.now(),
219
+ repeat_mode=repeat_mode
220
+ )
221
+ db.session.add(run)
222
+ db.session.commit()
223
+ run_id = run.id # Save the ID
224
+ try:
201
225
 
226
+ global_config.runner_status = {"id":run_id, "type": "workflow"}
202
227
  # Run "prep" section once
203
228
  self._run_actions(script, section_name="prep", logger=logger, socketio=socketio, run_id=run_id)
204
229
  output_list = []
@@ -215,35 +240,52 @@ class ScriptRunner:
215
240
  # Run "cleanup" section once
216
241
  self._run_actions(script, section_name="cleanup", logger=logger, socketio=socketio,run_id=run_id)
217
242
  # Reset the running flag when done
218
-
219
243
  # Save results if necessary
220
-
221
244
  if not script.python_script and output_list:
222
245
  filename = self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
223
246
  self._emit_progress(socketio, 100)
224
247
 
225
- except Exception as e:
226
- logger.error(f"Error during script execution: {e.__str__()}")
227
- error_flag = True
228
- finally:
229
- self.lock.release()
230
- with current_app.app_context():
231
- run = db.session.get(WorkflowRun, run_id)
232
- run.end_time = datetime.now()
233
- run.output_file = filename
234
- run.run_error = error_flag
235
- db.session.commit()
248
+ except Exception as e:
249
+ logger.error(f"Error during script execution: {e.__str__()}")
250
+ error_flag = True
251
+ finally:
252
+ self.lock.release()
253
+ with current_app.app_context():
254
+ run = db.session.get(WorkflowRun, run_id)
255
+ run.end_time = datetime.now()
256
+ run.data_path = filename
257
+ run.run_error = error_flag
258
+ db.session.commit()
236
259
 
237
260
 
238
261
  def _run_actions(self, script, section_name="", logger=None, socketio=None, run_id=None):
239
262
  _func_str = script.python_script or script.compile()
240
263
  step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
241
- logger.info(f'Executing {section_name} steps') if step_list else logger.info(f'No {section_name} steps')
264
+ if not step_list:
265
+ logger.info(f'No {section_name} steps')
266
+ return None
267
+
268
+ logger.info(f'Executing {section_name} steps')
242
269
  if self.stop_pending_event.is_set():
243
270
  logger.info(f"Stopping execution during {section_name} section.")
244
- return
245
- if step_list:
246
- self.exec_steps(script, section_name, logger, socketio, run_id=run_id, i_progress=0)
271
+ return None
272
+
273
+ phase = WorkflowPhase(
274
+ run_id=run_id,
275
+ name=section_name,
276
+ repeat_index=0,
277
+ start_time=datetime.now()
278
+ )
279
+ db.session.add(phase)
280
+ db.session.commit()
281
+ phase_id = phase.id
282
+
283
+ step_outputs = self.exec_steps(script, section_name, logger, socketio, phase_id=phase_id)
284
+ # Save phase-level output
285
+ phase.outputs = step_outputs
286
+ phase.end_time = datetime.now()
287
+ db.session.commit()
288
+ return step_outputs
247
289
 
248
290
  def _run_config_section(self, config, arg_type, output_list, script, run_name, logger, socketio, run_id, compiled=True):
249
291
  if not compiled:
@@ -266,10 +308,25 @@ class ScriptRunner:
266
308
  self._emit_progress(socketio, progress)
267
309
  # fname = f"{run_name}_script"
268
310
  # function = self.globals_dict[fname]
269
- output = self.exec_steps(script, "script", logger, socketio, run_id, i, **kwargs)
311
+
312
+ phase = WorkflowPhase(
313
+ run_id=run_id,
314
+ name="main",
315
+ repeat_index=i,
316
+ parameters=kwargs,
317
+ start_time=datetime.now()
318
+ )
319
+ db.session.add(phase)
320
+ db.session.commit()
321
+
322
+ phase_id = phase.id
323
+ output = self.exec_steps(script, "script", logger, socketio, phase_id, **kwargs)
270
324
  if output:
271
325
  # kwargs.update(output)
272
326
  output_list.append(output)
327
+ phase.outputs = {k:v for k, v in output.items() if k not in arg_type.keys()}
328
+ phase.end_time = datetime.now()
329
+ db.session.commit()
273
330
 
274
331
  def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, script, run_name, return_list, compiled,
275
332
  logger, socketio, history, output_path, run_id, optimizer=None):
@@ -306,6 +363,17 @@ class ScriptRunner:
306
363
  if self.stop_pending_event.is_set():
307
364
  logger.info(f'Stopping execution during {run_name}: {i_progress + 1}/{int(repeat_count)}')
308
365
  break
366
+
367
+ phase = WorkflowPhase(
368
+ run_id=run_id,
369
+ name="main",
370
+ repeat_index=i_progress,
371
+ start_time=datetime.now()
372
+ )
373
+ db.session.add(phase)
374
+ db.session.commit()
375
+ phase_id = phase.id
376
+
309
377
  logger.info(f'Executing {run_name} experiment: {i_progress + 1}/{int(repeat_count)}')
310
378
  progress = (i_progress + 1) * 100 / int(repeat_count) - 0.1
311
379
  self._emit_progress(socketio, progress)
@@ -315,7 +383,9 @@ class ScriptRunner:
315
383
  logger.info(f'Output value: {parameters}')
316
384
  # fname = f"{run_name}_script"
317
385
  # function = self.globals_dict[fname]
318
- output = self.exec_steps(script, "script", logger, socketio, run_id, i_progress, **parameters)
386
+ phase.parameters = parameters
387
+
388
+ output = self.exec_steps(script, "script", logger, socketio, phase_id, **parameters)
319
389
 
320
390
  _output = {key: value for key, value in output.items() if key in return_list}
321
391
  ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
@@ -327,7 +397,8 @@ class ScriptRunner:
327
397
  try:
328
398
  parameters = optimizer.suggest(1)
329
399
  logger.info(f'Output value: {parameters}')
330
- output = self.exec_steps(script, "script", logger, socketio, run_id, i_progress, **parameters)
400
+ phase.parameters = parameters
401
+ output = self.exec_steps(script, "script", logger, socketio, phase_id, **parameters)
331
402
  if output:
332
403
  optimizer.observe(output)
333
404
  output.update(parameters)
@@ -337,17 +408,20 @@ class ScriptRunner:
337
408
  else:
338
409
  # fname = f"{run_name}_script"
339
410
  # function = self.globals_dict[fname]
340
- output = self.exec_steps(script, "script", logger, socketio, run_id, i_progress)
411
+ output = self.exec_steps(script, "script", logger, socketio, phase_id)
341
412
 
342
413
  if output:
343
414
  output_list.append(output)
344
415
  logger.info(f'Output value: {output}')
416
+ phase.outputs = output
417
+ phase.end_time = datetime.now()
418
+ db.session.commit()
345
419
 
346
420
  if bo_args:
347
421
  ax_client.save_to_json_file(os.path.join(output_path, f"{run_name}_ax_client.json"))
348
- logger.info(
349
- f'Optimization complete. Results saved to {os.path.join(output_path, f"{run_name}_ax_client.json")}'
350
- )
422
+ logger.info(
423
+ f'Optimization complete. Results saved to {os.path.join(output_path, f"{run_name}_ax_client.json")}'
424
+ )
351
425
  return output_list
352
426
 
353
427
  @staticmethod
@@ -2,6 +2,7 @@ import threading
2
2
  import time
3
3
  from datetime import datetime
4
4
 
5
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
5
6
  from ivoryos.utils.db_models import db, SingleStep
6
7
  from ivoryos.utils.global_config import GlobalConfig
7
8
 
@@ -48,16 +49,20 @@ class TaskRunner:
48
49
  if component.startswith("deck."):
49
50
  component = component.split(".")[1]
50
51
  instrument = getattr(deck, component)
52
+ function_executable = getattr(instrument, method)
53
+ elif component.startswith("blocks."):
54
+ component = component.split(".")[1]
55
+ function_executable = BUILDING_BLOCKS[component][method]["func"]
51
56
  else:
52
57
  temp_connections = global_config.defined_variables
53
58
  instrument = temp_connections.get(component)
54
- function_executable = getattr(instrument, method)
59
+ function_executable = getattr(instrument, method)
55
60
  return function_executable
56
61
 
57
62
  def _run_single_step(self, component, method, kwargs, current_app=None):
58
63
  try:
59
64
  function_executable = self._get_executable(component, deck, method)
60
- method_name = f"{function_executable.__self__.__class__.__name__}.{function_executable.__name__}"
65
+ method_name = f"{component}.{method}"
61
66
  except Exception as e:
62
67
  self.lock.release()
63
68
  return {"status": "error", "msg": e.__str__()}
ivoryos/utils/utils.py CHANGED
@@ -15,7 +15,7 @@ from flask_login import current_user
15
15
  from flask_socketio import SocketIO
16
16
 
17
17
  from ivoryos.utils.db_models import Script
18
-
18
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
19
19
 
20
20
  def get_script_file():
21
21
  """Get script from Flask session and returns the script"""
@@ -151,6 +151,7 @@ def _convert_by_str(args, arg_types):
151
151
  return args
152
152
  except Exception:
153
153
  raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
154
+ return args
154
155
 
155
156
 
156
157
  def _convert_by_class(args, arg_types):
@@ -348,8 +349,8 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
348
349
  for name, class_type in items.items():
349
350
  print(f" {name}: {class_type}")
350
351
 
351
- print_section("✅ INCLUDED", deck_summary["included"])
352
- print_section("❌ FAILED", deck_summary["failed"])
352
+ print_section("✅ INCLUDED MODULES", deck_summary["included"])
353
+ print_section("❌ FAILED MODULES", deck_summary["failed"])
353
354
  print("\n")
354
355
 
355
356
  print_deck_snapshot(deck_summary)
@@ -364,6 +365,27 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
364
365
  return deck_snapshot
365
366
 
366
367
 
368
+ def create_block_snapshot(save: bool = False, output_path: str = ''):
369
+ block_snapshot = {}
370
+ included = {}
371
+ failed = {}
372
+ for category, data in BUILDING_BLOCKS.items():
373
+ key = f"blocks.{category}"
374
+ block_snapshot[key] = {}
375
+
376
+ for func_name, meta in data.items():
377
+ func = meta["func"]
378
+ block_snapshot[key][func_name] = {
379
+ "signature": meta["signature"],
380
+ "docstring": meta["docstring"],
381
+ "path": f"{func.__module__}.{func.__qualname__}"
382
+ }
383
+ if block_snapshot:
384
+ print(f"\n=== ✅ BUILDING_BLOCKS ({len(block_snapshot)}) ===")
385
+ for category, blocks in block_snapshot.items():
386
+ print(f" {category}: ", ",".join(blocks.keys()))
387
+ return block_snapshot
388
+
367
389
  def load_deck(pkl_name: str):
368
390
  """
369
391
  Loads a pickled deck snapshot from disk on offline mode
ivoryos/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.2.7"
1
+ __version__ = "1.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.2.7
3
+ Version: 1.3.0
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
6
6
  License: MIT
@@ -1,35 +1,37 @@
1
- ivoryos/__init__.py,sha256=BAA7OPl3h_QdSc4na-7OWTKhaOq1LtHOrJHX3gGITrc,9863
1
+ ivoryos/__init__.py,sha256=eUtNgSskl--l94VUTT1bgiBR8gdMMFQgjHEsHOxdHyI,320
2
+ ivoryos/app.py,sha256=tnFimgKnjRLhgIiraAVhEZFmcp6TGho7mQ5a5Y35rlY,4794
2
3
  ivoryos/config.py,sha256=y3RxNjiIola9tK7jg-mHM8EzLMwiLwOzoisXkDvj0gA,2174
4
+ ivoryos/server.py,sha256=K0_Ioui0uKshKl5fxGB_1wJD4OckXyR9DdOfCIhvkfE,6742
3
5
  ivoryos/socket_handlers.py,sha256=VWVWiIdm4jYAutwGu6R0t1nK5MuMyOCL0xAnFn06jWQ,1302
4
- ivoryos/version.py,sha256=49prCLbE3fFzLfxem5rd2dr1iV4_L-bN0N4J7jxU5yA,22
6
+ ivoryos/version.py,sha256=F5mW07pSyGrqDNY2Ehr-UpDzpBtN-FsYU0QGZWf6PJE,22
5
7
  ivoryos/optimizer/ax_optimizer.py,sha256=PoSu8hrDFFpqyhRBnaSMswIUsDfEX6sPWt8NEZ_sobs,7112
6
8
  ivoryos/optimizer/base_optimizer.py,sha256=JTbUharZKn0t8_BDbAFuwZIbT1VOnX1Xuog1pJuU8hY,1992
7
9
  ivoryos/optimizer/baybe_optimizer.py,sha256=EdrrRiYO-IOx610cPXiQhH4qG8knUP0uiZ0YoyaGIU8,7954
8
10
  ivoryos/optimizer/registry.py,sha256=lr0cqdI2iEjw227ZPRpVkvsdYdddjeJJRzawDv77cEc,219
9
11
  ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- ivoryos/routes/api/api.py,sha256=1Hq4FOBtSEXqjataoPUdAWHvezw07xqhEI1fJdoSn5U,2284
12
+ ivoryos/routes/api/api.py,sha256=97Y7pqTwOaWgZgI5ovEPxEBm6Asrt0Iy0VhBkVp2xqA,2304
11
13
  ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
14
  ivoryos/routes/auth/auth.py,sha256=CqoP9cM8BuXVGHGujX7-0sNAOdWILU9amyBrObOD6Ss,3283
13
15
  ivoryos/routes/auth/templates/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
14
16
  ivoryos/routes/auth/templates/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
15
17
  ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- ivoryos/routes/control/control.py,sha256=4wqY-68PCJkhdBXzj4sb3BceI0PKViPiTLGz8VP_rU8,6059
18
+ ivoryos/routes/control/control.py,sha256=6LnVF4mGgfLQvzmrSFxaFz9lBtBe4WnXlIouDxtaR2E,6230
17
19
  ivoryos/routes/control/control_file.py,sha256=3fQ9R8EcdqKs_hABn2EqRAB1xC2DHAT_q_pwsMIDDQI,864
18
20
  ivoryos/routes/control/control_new_device.py,sha256=mfJKg5JAOagIpUKbp2b5nRwvd2V3bzT3M0zIhIsEaFM,5456
19
- ivoryos/routes/control/utils.py,sha256=at11wA5HPAZN4BfMaymj1GKEvRTrqi4Wg6cTqUZJDjU,1155
20
- ivoryos/routes/control/templates/controllers.html,sha256=tgtTuns8S2Pf6XKeojinQZ1bz112ieRGLPF5-1cElfE,8030
21
+ ivoryos/routes/control/utils.py,sha256=XlhhqAtOj7n3XfHPDxJ8TvCV2K2I2IixB0CBkl1QeQc,1242
22
+ ivoryos/routes/control/templates/controllers.html,sha256=5hF3zcx5Rpy0Zaoq-5YGrR_TvPD9MGIa30fI4smEii0,9702
21
23
  ivoryos/routes/control/templates/controllers_new.html,sha256=eVeLABT39DWOIYrwWClw7sAD3lCoAGCznygPgFbQoRc,5945
22
24
  ivoryos/routes/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- ivoryos/routes/data/data.py,sha256=AoqCaIAK0f9hstF1pxlJFeK_J-wKbMfXWGNDUbaBFFk,4218
25
+ ivoryos/routes/data/data.py,sha256=wJCd9TytdYCeU6BaGEUhBQHRYo7yn9OagIWa2qwSZEo,5583
24
26
  ivoryos/routes/data/templates/workflow_database.html,sha256=ofvHcovpwmJXo1SFiSrL8I9kLU_3U1UxsJUUrQ2CJUU,4878
25
- ivoryos/routes/data/templates/workflow_view.html,sha256=72xKreX9WhYx-0n0cFf-CL-fJIWXPCIaTi_Aa8Tq3xg,3651
26
- ivoryos/routes/data/templates/components/step_card.html,sha256=9lKR4NCgU2v5Nbdv2uaJ-9aKibtiB_2-Y_kyHX6Ka1k,730
27
+ ivoryos/routes/data/templates/workflow_view.html,sha256=Ti17kzlPlYTmzx5MkdsPlXJ1_k6QgMYQBM6FHjG50go,12491
28
+ ivoryos/routes/data/templates/components/step_card.html,sha256=XWsr7qxAY76RCuQHETubWjWBlPgs2HkviH4ju6qfBKo,1923
27
29
  ivoryos/routes/design/__init__.py,sha256=zS3HXKaw0ALL5n6t_W1rUz5Uj5_tTQ-Y1VMXyzewvR0,113
28
- ivoryos/routes/design/design.py,sha256=BxCSyb9NHlm6SLi7iZITS-g4IyPyBK0ZVAeadPGG-Cw,17763
30
+ ivoryos/routes/design/design.py,sha256=xYDwtCdTcCd282guaIeNvfUFc5UsiypkQVpRvFqRujQ,18246
29
31
  ivoryos/routes/design/design_file.py,sha256=m4yku8fkpLUs4XvLJBqR5V-kyaGKbGB6ZoRxGbjEU5Q,2140
30
32
  ivoryos/routes/design/design_step.py,sha256=l8U3-FuXmap__sYm51AueKdbTaLCFaKjAz-j02b4g-E,5200
31
33
  ivoryos/routes/design/templates/experiment_builder.html,sha256=hh-d2tOc_40gww5WfUYIf8sM3qBaALZnR8Sx7Ja4tpU,1623
32
- ivoryos/routes/design/templates/components/action_form.html,sha256=ktnmXVwe2WFLM6Sg_VbBfDrPXrnongSUxjpYhZGamPY,3058
34
+ ivoryos/routes/design/templates/components/action_form.html,sha256=kXJOrJLbFsMHHWVSuMQHpt1xFrUMnwgzTG8e6Qfn0Cg,3042
33
35
  ivoryos/routes/design/templates/components/actions_panel.html,sha256=jHTR58saTUIZInBdC-vLc1ZTbStLiULeWbupjB4hQzo,977
34
36
  ivoryos/routes/design/templates/components/autofill_toggle.html,sha256=CRVQUHoQT7sOSO5-Vax54ImHdT4G_mEgqR5OQkeUwK8,617
35
37
  ivoryos/routes/design/templates/components/canvas.html,sha256=bKLCJaG1B36Yy9Vsnz4P5qiX4BPdfaGe9JeQQzu9rsI,268
@@ -38,7 +40,7 @@ ivoryos/routes/design/templates/components/canvas_header.html,sha256=7iIzLDGHX7M
38
40
  ivoryos/routes/design/templates/components/canvas_main.html,sha256=9inYO700zRa09lfQI2NY4FJGGeTh-9rvX4ltjj0LK3k,1432
39
41
  ivoryos/routes/design/templates/components/deck_selector.html,sha256=ryTRpljYezo0AzGLCJu_qOMokjjnft3GIxddmNGtBA0,657
40
42
  ivoryos/routes/design/templates/components/edit_action_form.html,sha256=Dz7FnnOK4PYptAHNy9_WFCU1RZTSV61-1lNHHOSRJNs,1876
41
- ivoryos/routes/design/templates/components/instruments_panel.html,sha256=r1jnScVRAknrRPRbAnIVApfnx9f4yavgf9ZlNhNpjW4,3135
43
+ ivoryos/routes/design/templates/components/instruments_panel.html,sha256=tRKd-wOqKjaMJCLuGgRmHtxIgSjklhBkuX8arm5aTCU,4268
42
44
  ivoryos/routes/design/templates/components/modals.html,sha256=6Dl8I8oD4ln7kK8C5e92pFVVH5KDte-vVTL0U_6NSTg,306
43
45
  ivoryos/routes/design/templates/components/python_code_overlay.html,sha256=GUHgsmUWQf0P1Fbg5W0OJC34TXCUIMQVUkS7KDoauyI,1264
44
46
  ivoryos/routes/design/templates/components/sidebar.html,sha256=A6dRo53zIB6QJVrRLJcBZHUNJ3qpYPnR3kWxM8gTkjw,501
@@ -76,25 +78,26 @@ ivoryos/static/js/action_handlers.js,sha256=UJHKFhYRNQRBo0AHLCIxhWxt8OSgYeyLynzP
76
78
  ivoryos/static/js/db_delete.js,sha256=l67fqUaN_FVDaL7v91Hd7LyRbxnqXx9nyjF34-7aewY,561
77
79
  ivoryos/static/js/overlay.js,sha256=dPxop19es0E0ZUSY3d_4exIk7CJuQEnlW5uTt5fZfzI,483
78
80
  ivoryos/static/js/script_metadata.js,sha256=m8VYZ8OGT2oTx1kXMXq60bKQI9WCbJNkzcFDzLvRuGc,1188
79
- ivoryos/static/js/socket_handler.js,sha256=2Iyv_3METjhSlSavs_L9FE3PKY4xDEpfzJpd2FywY9o,5300
81
+ ivoryos/static/js/socket_handler.js,sha256=vrpVyYMsFpHIJjqke5LwVttRI6IJMXSx_D0AMhWRg3k,6906
80
82
  ivoryos/static/js/sortable_card.js,sha256=ifmlGe3yy0U_KzMphV4ClRhK2DLOvkELYMlq1vECuac,807
81
- ivoryos/static/js/sortable_design.js,sha256=QqYyk385JNm6zCgZK_Oa-cJEP1uPtZ_tVz27x4hyx5A,4790
83
+ ivoryos/static/js/sortable_design.js,sha256=ASc9P6_423Mczeg6QH6LVtyxLyWhpxWJP2nEEjR9K1M,5474
82
84
  ivoryos/static/js/ui_state.js,sha256=XYsOcfGlduqLlqHySvPrRrR50CiAsml51duqneigsRY,3368
83
85
  ivoryos/templates/base.html,sha256=cl5w6E8yskbUzdiJFal6fZjnPuFNKEzc7BrrbRd6bMI,8581
84
86
  ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
87
  ivoryos/utils/bo_campaign.py,sha256=Fil-zT7JexL_p9XqyWByjAk42XB1R9XUKN8CdV5bi6c,9714
86
88
  ivoryos/utils/client_proxy.py,sha256=74G3HAuq50iEHkSvlMZFmQaukm613FbRgOdzO_T3dMg,10191
87
- ivoryos/utils/db_models.py,sha256=EN0gNzYgCxKLxgceKEixWi17EKMObz0hLdDnpZ-ur5o,27923
88
- ivoryos/utils/form.py,sha256=A6juCWGtSbaTClgVc8rqufniPRWT7LjeDQs4r0gQs50,22207
89
- ivoryos/utils/global_config.py,sha256=zNO9GYhGn7El3msWoxJIm3S4Mzb3VMh2i5ZEsVtvb2Q,2463
89
+ ivoryos/utils/db_models.py,sha256=TaRA65Zmj2lIqQk5sDoQzP_od6QnPUYJgvDAV9LkqYM,31074
90
+ ivoryos/utils/decorators.py,sha256=p1Bdl3dCeaHNv6-cCCUOZMiFu9kRaqqQnkFJUkzPoJE,991
91
+ ivoryos/utils/form.py,sha256=Ej9tx06KZZ5fPQm1ho1byotNocF3u24aatc2ZyI0rK4,22301
92
+ ivoryos/utils/global_config.py,sha256=D6oz5dttyaP24jbqnw1sR64moSb-7jJkSpRuufdA_TI,2747
90
93
  ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
91
94
  ivoryos/utils/py_to_json.py,sha256=fyqjaxDHPh-sahgT6IHSn34ktwf6y51_x1qvhbNlH-U,7314
92
- ivoryos/utils/script_runner.py,sha256=MdLMSAeaVXxnbcQfzHJximeJ6W7uOCxqTQhFSpsEieg,17065
95
+ ivoryos/utils/script_runner.py,sha256=TksUOaisnGj-A3lT78Ni6ilK_huc96WSq2uUPEcxdek,19354
93
96
  ivoryos/utils/serilize.py,sha256=lkBhkz8r2bLmz2_xOb0c4ptSSOqjIu6krj5YYK4Nvj8,6784
94
- ivoryos/utils/task_runner.py,sha256=qgHheE2rnhgRmWUeUQHgdS-Kl-yv-9uA-eM6lD9d0b4,3018
95
- ivoryos/utils/utils.py,sha256=-WiU0_brszB9yDsiQepf_7SzNgPTSpul2RSKDOY3pqo,13921
96
- ivoryos-1.2.7.dist-info/licenses/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
97
- ivoryos-1.2.7.dist-info/METADATA,sha256=EEA3X0SiKMnSGTiZ-Q5_4A6LsofsYSVdKE7L2o4eZhE,7351
98
- ivoryos-1.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
- ivoryos-1.2.7.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
100
- ivoryos-1.2.7.dist-info/RECORD,,
97
+ ivoryos/utils/task_runner.py,sha256=bfG6GubdlzgD8rBwzD00aGB5LDFmb9hLFJIOMH8hVv4,3248
98
+ ivoryos/utils/utils.py,sha256=09VPNRaIoA-mp1TXLGC3BwM2tDaAJ36csvNtW19KsU0,14792
99
+ ivoryos-1.3.0.dist-info/licenses/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
100
+ ivoryos-1.3.0.dist-info/METADATA,sha256=OanJGtMTbt4C6D3IFz-GprxXAME0PkEPEfBLliu4Jfw,7351
101
+ ivoryos-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
+ ivoryos-1.3.0.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
103
+ ivoryos-1.3.0.dist-info/RECORD,,