ivoryos 1.0.0__py3-none-any.whl → 1.0.3__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.

@@ -5,8 +5,8 @@ import threading
5
5
  import time
6
6
  from datetime import datetime
7
7
 
8
- from ivoryos.utils import utils
9
- from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep, db
8
+ from ivoryos.utils import utils, bo_campaign
9
+ from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep, db, SingleStep
10
10
  from ivoryos.utils.global_config import GlobalConfig
11
11
 
12
12
  global_config = GlobalConfig()
@@ -26,8 +26,9 @@ class ScriptRunner:
26
26
  self.stop_pending_event = threading.Event()
27
27
  self.stop_current_event = threading.Event()
28
28
  self.is_running = False
29
- self.lock = threading.Lock()
29
+ self.lock = global_config.runner_lock
30
30
  self.paused = False
31
+ self.current_app = None
31
32
 
32
33
  def toggle_pause(self):
33
34
  """Toggles between pausing and resuming the script"""
@@ -59,23 +60,29 @@ class ScriptRunner:
59
60
  self.stop_current_event.set()
60
61
  self.abort_pending()
61
62
 
63
+
62
64
  def run_script(self, script, repeat_count=1, run_name=None, logger=None, socketio=None, config=None, bo_args=None,
63
- output_path="", current_app=None):
64
- # Get run.id
65
+ output_path="", compiled=False, current_app=None):
65
66
  global deck
66
67
  if deck is None:
67
68
  deck = global_config.deck
68
- time.sleep(1)
69
- with self.lock:
70
- if self.is_running:
69
+
70
+ if self.current_app is None:
71
+ self.current_app = current_app
72
+ # time.sleep(1) # Optional: may help ensure deck readiness
73
+
74
+ # Try to acquire lock without blocking
75
+ if not self.lock.acquire(blocking=False):
76
+ if logger:
71
77
  logger.info("System is busy. Please wait for it to finish or stop it before starting a new one.")
72
- return None
73
- self.is_running = True
78
+ return None
74
79
 
75
80
  self.reset_stop_event()
76
81
 
77
- thread = threading.Thread(target=self._run_with_stop_check,
78
- args=(script, repeat_count, run_name, logger, socketio, config, bo_args, output_path, current_app))
82
+ thread = threading.Thread(
83
+ target=self._run_with_stop_check,
84
+ args=(script, repeat_count, run_name, logger, socketio, config, bo_args, output_path, current_app, compiled)
85
+ )
79
86
  thread.start()
80
87
  return thread
81
88
 
@@ -86,7 +93,7 @@ class ScriptRunner:
86
93
  :param kwargs: Arguments to pass to the function
87
94
  :return: The final result of the function execution
88
95
  """
89
- _func_str = script.compile()
96
+ _func_str = script.python_script or script.compile()
90
97
  step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
91
98
  global deck
92
99
  # global deck, registered_workflows
@@ -138,6 +145,8 @@ class ScriptRunner:
138
145
  method_name=method_name,
139
146
  start_time=start_time,
140
147
  )
148
+ db.session.add(step)
149
+ db.session.commit()
141
150
  logger.info(f"Executing: {line}")
142
151
  socketio.emit('execution', {'section': f"{section_name}-{index}"})
143
152
  # self._emit_progress(socketio, 100)
@@ -158,7 +167,7 @@ class ScriptRunner:
158
167
  step.run_error = True
159
168
  self.toggle_pause()
160
169
  step.end_time = datetime.now()
161
- db.session.add(step)
170
+ # db.session.add(step)
162
171
  db.session.commit()
163
172
 
164
173
  self.pause_event.wait()
@@ -173,7 +182,7 @@ class ScriptRunner:
173
182
  return exec_locals # Return the 'results' variable
174
183
 
175
184
  def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
176
- output_path, current_app):
185
+ output_path, current_app, compiled):
177
186
  time.sleep(1)
178
187
  # _func_str = script.compile()
179
188
  # step_list_dict: dict = script.convert_to_lines(_func_str)
@@ -183,11 +192,12 @@ class ScriptRunner:
183
192
  script_dict = script.script_dict
184
193
  with current_app.app_context():
185
194
 
186
- run = WorkflowRun(name=script.name or "untitled", platform=script.deck,start_time=datetime.now())
195
+ run = WorkflowRun(name=script.name or "untitled", platform=script.deck or "deck",start_time=datetime.now())
187
196
  db.session.add(run)
188
- db.session.flush()
189
-
190
- self._run_actions(script, section_name="prep", logger=logger, socketio=socketio, run_id=run.id)
197
+ db.session.commit()
198
+ run_id = run.id # Save the ID
199
+ global_config.runner_status = {"id":run_id, "type": "workflow"}
200
+ self._run_actions(script, section_name="prep", logger=logger, socketio=socketio, run_id=run_id)
191
201
  output_list = []
192
202
  _, arg_type = script.config("script")
193
203
  _, return_list = script.config_return()
@@ -195,43 +205,46 @@ class ScriptRunner:
195
205
  # Run "script" section multiple times
196
206
  if repeat_count:
197
207
  self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, script,
198
- run_name, return_list, logger, socketio, run_id=run.id)
208
+ run_name, return_list, compiled, logger, socketio, run_id=run_id)
199
209
  elif config:
200
210
  self._run_config_section(config, arg_type, output_list, script, run_name, logger,
201
- socketio, run_id=run.id)
211
+ socketio, run_id=run_id, compiled=compiled)
202
212
 
203
213
  # Run "cleanup" section once
204
- self._run_actions(script, section_name="cleanup", logger=logger, socketio=socketio,run_id=run.id)
214
+ self._run_actions(script, section_name="cleanup", logger=logger, socketio=socketio,run_id=run_id)
205
215
  # Reset the running flag when done
206
- with self.lock:
207
- self.is_running = False
216
+ self.lock.release()
208
217
  # Save results if necessary
209
218
  filename = None
210
- if output_list:
219
+ if not script.python_script and output_list:
211
220
  filename = self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
212
221
  self._emit_progress(socketio, 100)
222
+ with current_app.app_context():
223
+ run = db.session.get(WorkflowRun, run_id) # SQLAlchemy 1.4+ recommended method
213
224
  run.end_time = datetime.now()
214
225
  run.data_path = filename
215
226
  db.session.commit()
216
227
 
217
228
  def _run_actions(self, script, section_name="", logger=None, socketio=None, run_id=None):
218
- _func_str = script.compile()
229
+ _func_str = script.python_script or script.compile()
219
230
  step_list: list = script.convert_to_lines(_func_str).get(section_name, [])
220
231
  logger.info(f'Executing {section_name} steps') if step_list else logger.info(f'No {section_name} steps')
221
232
  if self.stop_pending_event.is_set():
222
233
  logger.info(f"Stopping execution during {section_name} section.")
223
234
  return
224
- self.exec_steps(script, section_name, logger, socketio, run_id=run_id, i_progress=0)
235
+ if step_list:
236
+ self.exec_steps(script, section_name, logger, socketio, run_id=run_id, i_progress=0)
225
237
 
226
- def _run_config_section(self, config, arg_type, output_list, script, run_name, logger, socketio, run_id):
227
- compiled = True
228
- for i in config:
229
- try:
230
- i = utils.convert_config_type(i, arg_type)
231
- except Exception as e:
232
- logger.info(e)
233
- compiled = False
234
- break
238
+ def _run_config_section(self, config, arg_type, output_list, script, run_name, logger, socketio, run_id, compiled=True):
239
+ if not compiled:
240
+ for i in config:
241
+ try:
242
+ i = utils.convert_config_type(i, arg_type)
243
+ compiled = True
244
+ except Exception as e:
245
+ logger.info(e)
246
+ compiled = False
247
+ break
235
248
  if compiled:
236
249
  for i, kwargs in enumerate(config):
237
250
  kwargs = dict(kwargs)
@@ -248,11 +261,14 @@ class ScriptRunner:
248
261
  # kwargs.update(output)
249
262
  output_list.append(output)
250
263
 
251
- def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, script, run_name, return_list,
264
+ def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, script, run_name, return_list, compiled,
252
265
  logger, socketio, run_id):
253
266
  if bo_args:
254
267
  logger.info('Initializing optimizer...')
255
- ax_client = utils.ax_initiation(bo_args, arg_types)
268
+ if compiled:
269
+ ax_client = bo_campaign.ax_init_opc(bo_args)
270
+ else:
271
+ ax_client = bo_campaign.ax_init_form(bo_args, arg_types)
256
272
  for i_progress in range(int(repeat_count)):
257
273
  if self.stop_pending_event.is_set():
258
274
  logger.info(f'Stopping execution during {run_name}: {i_progress + 1}/{int(repeat_count)}')
@@ -311,9 +327,9 @@ class ScriptRunner:
311
327
 
312
328
  def get_status(self):
313
329
  """Returns current status of the script runner."""
314
- with self.lock:
330
+ with self.current_app.app_context():
315
331
  return {
316
- "is_running": self.is_running,
332
+ "is_running": self.lock.locked(),
317
333
  "paused": self.paused,
318
334
  "stop_pending": self.stop_pending_event.is_set(),
319
335
  "stop_current": self.stop_current_event.is_set(),
@@ -0,0 +1,81 @@
1
+ import threading
2
+ import time
3
+ from datetime import datetime
4
+
5
+ from ivoryos.utils.db_models import db, SingleStep
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+
8
+ global_config = GlobalConfig()
9
+ global deck
10
+ deck = None
11
+
12
+
13
+ class TaskRunner:
14
+ def __init__(self, globals_dict=None):
15
+ self.retry = False
16
+ if globals_dict is None:
17
+ globals_dict = globals()
18
+ self.globals_dict = globals_dict
19
+ self.lock = global_config.runner_lock
20
+
21
+
22
+ def run_single_step(self, component, method, kwargs, wait=True, current_app=None):
23
+ global deck
24
+ if deck is None:
25
+ deck = global_config.deck
26
+
27
+ # Try to acquire lock without blocking
28
+ if not self.lock.acquire(blocking=False):
29
+ current_status = global_config.runner_status
30
+ current_status["status"] = "busy"
31
+ return current_status
32
+
33
+
34
+ if wait:
35
+ output = self._run_single_step(component, method, kwargs, current_app)
36
+ else:
37
+ print("running with thread")
38
+ thread = threading.Thread(
39
+ target=self._run_single_step, args=(component, method, kwargs, current_app)
40
+ )
41
+ thread.start()
42
+ time.sleep(0.1)
43
+ output = {"status": "task started", "task_id": global_config.runner_status.get("id")}
44
+
45
+ return output
46
+
47
+ def _get_executable(self, component, deck, method):
48
+ if component.startswith("deck."):
49
+ component = component.split(".")[1]
50
+ instrument = getattr(deck, component)
51
+ else:
52
+ temp_connections = global_config.defined_variables
53
+ instrument = temp_connections.get(component)
54
+ function_executable = getattr(instrument, method)
55
+ return function_executable
56
+
57
+ def _run_single_step(self, component, method, kwargs, current_app=None):
58
+ try:
59
+ function_executable = self._get_executable(component, deck, method)
60
+ method_name = f"{function_executable.__self__.__class__.__name__}.{function_executable.__name__}"
61
+ except Exception as e:
62
+ self.lock.release()
63
+ return {"status": "error", "msg": e.__str__()}
64
+
65
+ # with self.lock:
66
+ with current_app.app_context():
67
+ step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=False, start_time=datetime.now())
68
+ db.session.add(step)
69
+ db.session.commit()
70
+ global_config.runner_status = {"id":step.id, "type": "task"}
71
+ try:
72
+ output = function_executable(**kwargs)
73
+ step.output = output
74
+ step.end_time = datetime.now()
75
+ except Exception as e:
76
+ step.run_error = e.__str__()
77
+ step.end_time = datetime.now()
78
+ finally:
79
+ db.session.commit()
80
+ self.lock.release()
81
+ return output
ivoryos/utils/utils.py CHANGED
@@ -240,74 +240,6 @@ def start_logger(socketio: SocketIO, logger_name: str, log_filename: str = None)
240
240
  return logger
241
241
 
242
242
 
243
- def ax_wrapper(data: dict, arg_types: list):
244
- """
245
- Ax platform wrapper function for creating optimization campaign parameters and objective from the web form input
246
- :param data: e.g.,
247
- {
248
- "param_1_type": "range", "param_1_value": [1,2],
249
- "param_2_type": "range", "param_2_value": [1,2],
250
- "obj_1_min": True,
251
- "obj_2_min": True
252
- }
253
- :return: the optimization campaign parameters
254
- parameter=[
255
- {"name": "param_1", "type": "range", "bounds": [1,2]},
256
- {"name": "param_1", "type": "range", "bounds": [1,2]}
257
- ]
258
- objectives=[
259
- {"name": "obj_1", "min": True, "threshold": None},
260
- {"name": "obj_2", "min": True, "threshold": None},
261
- ]
262
- """
263
- from ax.service.utils.instantiation import ObjectiveProperties
264
- parameter = []
265
- objectives = {}
266
- # Iterate through the webui_data dictionary
267
- for key, value in data.items():
268
- # Check if the key corresponds to a parameter type
269
- if "_type" in key:
270
- param_name = key.split("_type")[0]
271
- param_type = value
272
- param_value = data[f"{param_name}_value"].split(",")
273
- try:
274
- values = [float(v) for v in param_value]
275
- except Exception:
276
- values = param_value
277
- if param_type == "range":
278
- param = {"name": param_name, "type": param_type, "bounds": values}
279
- if param_type == "choice":
280
- param = {"name": param_name, "type": param_type, "values": values}
281
- if param_type == "fixed":
282
- param = {"name": param_name, "type": param_type, "value": values[0]}
283
- _type = arg_types[param_name] if arg_types[param_name] in ["str", "bool", "int"] else "float"
284
- param.update({"value_type": _type})
285
- parameter.append(param)
286
- elif key.endswith("_min"):
287
- if not value == 'none':
288
- obj_name = key.split("_min")[0]
289
- is_min = True if value == "minimize" else False
290
-
291
- threshold = None if f"{obj_name}_threshold" not in data else data[f"{obj_name}_threshold"]
292
- properties = ObjectiveProperties(minimize=is_min)
293
- objectives[obj_name] = properties
294
-
295
- return parameter, objectives
296
-
297
-
298
- def ax_initiation(data, arg_types):
299
- """
300
- create Ax campaign from the web form input
301
- :param data:
302
- """
303
- install_and_import("ax", "ax-platform")
304
- parameter, objectives = ax_wrapper(data, arg_types)
305
- from ax.service.ax_client import AxClient
306
- ax_client = AxClient()
307
- ax_client.create_experiment(parameter, objectives=objectives)
308
- return ax_client
309
-
310
-
311
243
  def get_arg_type(args, parameters):
312
244
  """get argument type from signature"""
313
245
  arg_types = {}
ivoryos/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.0.0"
1
+ __version__ = "1.0.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 1.0.0
3
+ Version: 1.0.3
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Home-page: https://gitlab.com/heingroup/ivoryos
6
6
  Author: Ivory Zhang
@@ -22,8 +22,8 @@ Requires-Dist: python-dotenv
22
22
  [![PyPI version](https://img.shields.io/pypi/v/ivoryos)](https://pypi.org/project/ivoryos/)
23
23
  ![License](https://img.shields.io/pypi/l/ivoryos)
24
24
  [![YouTube](https://img.shields.io/badge/YouTube-video-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
25
- [![Published](https://img.shields.io/badge/Nature_Communications-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
26
-
25
+ [![Published](https://img.shields.io/badge/Nature_Comm.-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
26
+ [![Discord](https://img.shields.io/discord/1313641159356059770?label=Discord&logo=discord&color=5865F2)](https://discord.gg/AX5P9EdGVX)
27
27
 
28
28
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
29
29
  # ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
@@ -1,26 +1,26 @@
1
1
  ivoryos/__init__.py,sha256=1v6LwuIao8WbmSskifzdfde7E_gH3PWCYaNpLaRtYZk,7341
2
2
  ivoryos/config.py,sha256=3FPBYTIBhQTKDvsEoR8ZeTmg65D-CSFEdGmOuIL4pSI,1311
3
- ivoryos/version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
3
+ ivoryos/version.py,sha256=2plzdEEb24FLjE2I2XyBBcJEPYWHccNL4SgtLC_6erg,22
4
4
  ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- ivoryos/routes/auth/auth.py,sha256=7CdXjGAr1B_xsmwweakTWOoROgsOJf0MNTzlMP_5Nus,3240
6
+ ivoryos/routes/auth/auth.py,sha256=rvqMf4oeYXIjcsNWPbHoxK4QJRbM4YSMCl3IDRMnYtM,3255
7
7
  ivoryos/routes/auth/templates/auth/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
8
8
  ivoryos/routes/auth/templates/auth/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
9
9
  ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- ivoryos/routes/control/control.py,sha256=bxNYR-VAFA7tSswDt5k130AnIx1f64_R9N2B9kMridI,14160
10
+ ivoryos/routes/control/control.py,sha256=AaXqXqaIIed9xBVQsk52CNnNDTSofje5qQAfDGWd1Q0,16404
11
11
  ivoryos/routes/control/templates/control/controllers.html,sha256=iIp0h6WA68gQj9OsoiB7dU1BqH8CGomTueR73F4C8eY,4274
12
- ivoryos/routes/control/templates/control/controllers_home.html,sha256=VQ77HRvBlyBrQ3al5fcKF5Y6_vKtU8WeAhilqQQltAo,2997
12
+ ivoryos/routes/control/templates/control/controllers_home.html,sha256=qAM4iZBEuXvSgGUWWVVIe2E9MPJOeG7U214hYM84jIE,2976
13
13
  ivoryos/routes/control/templates/control/controllers_new.html,sha256=uOQo9kYmwX2jk3KZDkMUF_ylfNUIs_oIWb_kk_MMVDM,4921
14
14
  ivoryos/routes/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- ivoryos/routes/database/database.py,sha256=uchoLXRx8ShJZIu20NZya6FJ4LMg2b9yvn6hjEq3dlU,7687
16
- ivoryos/routes/database/templates/database/experiment_database.html,sha256=edlCcKfrS91gGG1dPFQjC9xD7F7nWNNqS3S6Oa7apzs,3460
17
- ivoryos/routes/database/templates/database/experiment_step_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
15
+ ivoryos/routes/database/database.py,sha256=kP5dEb5TvX3GlePk2sxlpwer8AsTM36kdeCkzAiFkR8,9837
16
+ ivoryos/routes/database/templates/database/scripts_database.html,sha256=tEpKOj1UGz7mcDq11guwP48XJxwawjxjPvxRhKiIG2I,3640
18
17
  ivoryos/routes/database/templates/database/step_card.html,sha256=F4JRfacrEQfk2rrEbcI_i7G84nzKKDmCrMSmStLb4W4,290
19
- ivoryos/routes/database/templates/database/workflow_run_database.html,sha256=MczK9my9u0SyQsMFLbc6CXeZqKaBo5vk1SpwjkcZdqk,3571
18
+ ivoryos/routes/database/templates/database/workflow_database.html,sha256=fsJHrYeEHGBKRn1pIxWITE6e93tdZsXH3zRs9Ob5FX0,4467
19
+ ivoryos/routes/database/templates/database/workflow_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
20
20
  ivoryos/routes/design/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- ivoryos/routes/design/design.py,sha256=ViVCys-yPv_W4B2JrLT9A4jf8_2N_QDHgbEVUwGFre4,25334
21
+ ivoryos/routes/design/design.py,sha256=Ju9Hz0khhAk6HmnZd3Er3PglFkvZBNPAJ-fPJMfrrIM,29328
22
22
  ivoryos/routes/design/templates/design/experiment_builder.html,sha256=rEdcHj5onJG_4MejdFBPnJVzsvCMp1KDteqNkpx24kQ,29430
23
- ivoryos/routes/design/templates/design/experiment_run.html,sha256=Q7cYYTgvZ8SBzqkDEhAwR634-LLcYq4Gof4bpH_adt0,30397
23
+ ivoryos/routes/design/templates/design/experiment_run.html,sha256=7VP0Vo98phcYnFennd5vqaMK1M1QBwDmM-b9aZb8jOw,26282
24
24
  ivoryos/routes/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  ivoryos/routes/main/main.py,sha256=yuVJzXAob1kc1dfflkTBIZQ0tdf6kChfuq-uQlN1e9Q,957
26
26
  ivoryos/routes/main/templates/main/help.html,sha256=IOktMEsOPk0SCiMBXZ4mpffClERAyX8W82fel71M3M0,9370
@@ -31,20 +31,22 @@ ivoryos/static/style.css,sha256=zQVx35A5g6JMJ-K84-6fSKtzXGjp_p5ZVG6KLHPM2IE,4021
31
31
  ivoryos/static/gui_annotation/Slide1.png,sha256=Lm4gdOkUF5HIUFaB94tl6koQVkzpitKj43GXV_XYMMc,121727
32
32
  ivoryos/static/gui_annotation/Slide2.PNG,sha256=z3wQ9oVgg4JTWVLQGKK_KhtepRHUYP1e05XUWGT2A0I,118761
33
33
  ivoryos/static/js/overlay.js,sha256=dPxop19es0E0ZUSY3d_4exIk7CJuQEnlW5uTt5fZfzI,483
34
- ivoryos/static/js/socket_handler.js,sha256=YGwWlT8TqNBvvIzs2G9g1g7nM2-vUPZjCwmQt4Yv0Uw,5078
34
+ ivoryos/static/js/socket_handler.js,sha256=2Iyv_3METjhSlSavs_L9FE3PKY4xDEpfzJpd2FywY9o,5300
35
35
  ivoryos/static/js/sortable_card.js,sha256=ifmlGe3yy0U_KzMphV4ClRhK2DLOvkELYMlq1vECuac,807
36
36
  ivoryos/static/js/sortable_design.js,sha256=wwpKfIzZGDxfX3moNz0cvPvm9YyHmopZK3wmkUdnBiw,4333
37
37
  ivoryos/templates/base.html,sha256=sDdwqOIUP2Get-py4E59PkieoGWLFpX6wAJe93s4aRo,8518
38
38
  ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- ivoryos/utils/client_proxy.py,sha256=AzcSQGMqeCqVULP1a7vEKNe135NZYryVX63ke0wgK04,2099
40
- ivoryos/utils/db_models.py,sha256=1G2De1nLRCGwuY4zqgMeIQ-p1XJ_PkBxH1cd0fJ9YgY,26740
39
+ ivoryos/utils/bo_campaign.py,sha256=CVs7q15Pm2SRuJNaCvZKIxOFuv1xibM2yymtpAMAWOk,3285
40
+ ivoryos/utils/client_proxy.py,sha256=0OT2xTMkqh_2ybgCxMV_71ZVUThWwrsnAhTIBY5vDR8,2095
41
+ ivoryos/utils/db_models.py,sha256=zlmmD2600CYyn79gQq8k0Vra7BDBKJBAyNLYclIWdvs,27382
41
42
  ivoryos/utils/form.py,sha256=b3JKxRc1jN45-bXyfzSJT1lcssUuxT86FhRmNUDv5-U,20973
42
- ivoryos/utils/global_config.py,sha256=P0xs_33bZfNQ-D71lCkq7HJyT4ngQWPqUKnkoMrmM8c,1908
43
+ ivoryos/utils/global_config.py,sha256=OqfDrPgOzRdIUMD4V3pA9t6b-BATMjGZl8Jn7nkI56k,2138
43
44
  ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
44
- ivoryos/utils/script_runner.py,sha256=WkLGO0tI6Bh1iJatJgYt00LcjoOYdAPCjDfYbyvqRN8,13649
45
- ivoryos/utils/utils.py,sha256=pVdhe3RksaxwRLEaKq-Q1hl7oMi9f0K1LpPusevak2s,16234
46
- ivoryos-1.0.0.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
47
- ivoryos-1.0.0.dist-info/METADATA,sha256=PB9x16E_gTeKyJPzoczzkZ4_KaPbeSJSYPGkl0tSVS8,6866
48
- ivoryos-1.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
49
- ivoryos-1.0.0.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
50
- ivoryos-1.0.0.dist-info/RECORD,,
45
+ ivoryos/utils/script_runner.py,sha256=0b5hLKAF2o0SQKiArhUsG8-4MA-eniAcjwi8gCNVwtY,14542
46
+ ivoryos/utils/task_runner.py,sha256=u4nF0wOADu_HVlGYVTOXnUm1woWGgYAccr-ZCzgtb6Q,2899
47
+ ivoryos/utils/utils.py,sha256=OBwrRu02yh7pqG_lyl10zWr_RYes3xhMporxIz8lGYI,13579
48
+ ivoryos-1.0.3.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
49
+ ivoryos-1.0.3.dist-info/METADATA,sha256=XE0dH-qpGIUYCXwkzDULsuTLOJPlGj0AnUwhtjeiQPY,6992
50
+ ivoryos-1.0.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
51
+ ivoryos-1.0.3.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
52
+ ivoryos-1.0.3.dist-info/RECORD,,