ivoryos 1.2.7__tar.gz → 1.2.8__tar.gz

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 (109) hide show
  1. {ivoryos-1.2.7 → ivoryos-1.2.8}/PKG-INFO +1 -1
  2. ivoryos-1.2.8/ivoryos/__init__.py +13 -0
  3. ivoryos-1.2.8/ivoryos/app.py +94 -0
  4. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/control.py +8 -6
  5. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/templates/controllers.html +18 -0
  6. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/utils.py +2 -0
  7. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/design.py +11 -4
  8. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/action_form.html +2 -2
  9. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
  10. ivoryos-1.2.7/ivoryos/__init__.py → ivoryos-1.2.8/ivoryos/server.py +18 -98
  11. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/socket_handler.js +39 -4
  12. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/sortable_design.js +28 -11
  13. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/db_models.py +40 -5
  14. ivoryos-1.2.8/ivoryos/utils/decorators.py +33 -0
  15. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/form.py +4 -2
  16. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/global_config.py +10 -0
  17. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/script_runner.py +21 -2
  18. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/task_runner.py +7 -2
  19. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/utils.py +20 -1
  20. ivoryos-1.2.8/ivoryos/version.py +1 -0
  21. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos.egg-info/PKG-INFO +1 -1
  22. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos.egg-info/SOURCES.txt +3 -0
  23. ivoryos-1.2.7/ivoryos/version.py +0 -1
  24. {ivoryos-1.2.7 → ivoryos-1.2.8}/LICENSE +0 -0
  25. {ivoryos-1.2.7 → ivoryos-1.2.8}/README.md +0 -0
  26. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/config.py +0 -0
  27. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/optimizer/ax_optimizer.py +0 -0
  28. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/optimizer/base_optimizer.py +0 -0
  29. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/optimizer/baybe_optimizer.py +0 -0
  30. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/optimizer/registry.py +0 -0
  31. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/__init__.py +0 -0
  32. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/api/api.py +0 -0
  33. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/auth/__init__.py +0 -0
  34. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/auth/auth.py +0 -0
  35. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/auth/templates/login.html +0 -0
  36. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/auth/templates/signup.html +0 -0
  37. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/__init__.py +0 -0
  38. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/control_file.py +0 -0
  39. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/control_new_device.py +0 -0
  40. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/control/templates/controllers_new.html +0 -0
  41. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/data/__init__.py +0 -0
  42. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/data/data.py +0 -0
  43. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/data/templates/components/step_card.html +0 -0
  44. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/data/templates/workflow_database.html +0 -0
  45. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/data/templates/workflow_view.html +0 -0
  46. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/__init__.py +0 -0
  47. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/design_file.py +0 -0
  48. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/design_step.py +0 -0
  49. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
  50. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
  51. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas.html +0 -0
  52. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
  53. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
  54. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
  55. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
  56. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
  57. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
  58. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
  59. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
  60. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
  61. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
  62. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals.html +0 -0
  63. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
  64. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
  65. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
  66. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
  67. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/__init__.py +0 -0
  68. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/execute.py +0 -0
  69. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/execute_file.py +0 -0
  70. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
  71. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
  72. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
  73. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
  74. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
  75. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
  76. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
  77. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
  78. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
  79. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/library/__init__.py +0 -0
  80. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/library/library.py +0 -0
  81. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/library/templates/library.html +0 -0
  82. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/main/__init__.py +0 -0
  83. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/main/main.py +0 -0
  84. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/main/templates/help.html +0 -0
  85. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/routes/main/templates/home.html +0 -0
  86. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/socket_handlers.py +0 -0
  87. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/favicon.ico +0 -0
  88. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  89. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  90. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/action_handlers.js +0 -0
  91. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/db_delete.js +0 -0
  92. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/overlay.js +0 -0
  93. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/script_metadata.js +0 -0
  94. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/sortable_card.js +0 -0
  95. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/js/ui_state.js +0 -0
  96. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/logo.webp +0 -0
  97. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/static/style.css +0 -0
  98. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/templates/base.html +0 -0
  99. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/__init__.py +0 -0
  100. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/bo_campaign.py +0 -0
  101. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/client_proxy.py +0 -0
  102. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/llm_agent.py +0 -0
  103. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/py_to_json.py +0 -0
  104. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos/utils/serilize.py +0 -0
  105. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos.egg-info/dependency_links.txt +0 -0
  106. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos.egg-info/requires.txt +0 -0
  107. {ivoryos-1.2.7 → ivoryos-1.2.8}/ivoryos.egg-info/top_level.txt +0 -0
  108. {ivoryos-1.2.7 → ivoryos-1.2.8}/pyproject.toml +0 -0
  109. {ivoryos-1.2.7 → ivoryos-1.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.2.7
3
+ Version: 1.2.8
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
@@ -0,0 +1,13 @@
1
+ from ivoryos.server import run
2
+ from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
3
+ from ivoryos.version import __version__ as ivoryos_version
4
+ from ivoryos.utils.decorators import block, BUILDING_BLOCKS
5
+
6
+
7
+ __all__ = [
8
+ "block",
9
+ "BUILDING_BLOCKS",
10
+ "OPTIMIZER_REGISTRY",
11
+ "run",
12
+ "ivoryos_version",
13
+ ]
@@ -0,0 +1,94 @@
1
+ import os
2
+ import uuid
3
+
4
+ from flask import Flask, session, g, redirect, url_for
5
+ from flask_login import AnonymousUserMixin
6
+
7
+ from ivoryos.utils import utils
8
+ from ivoryos.utils.db_models import db
9
+ from ivoryos.config import Config, get_config
10
+ from ivoryos.routes.auth.auth import auth, login_manager
11
+ from ivoryos.routes.control.control import control
12
+ from ivoryos.routes.data.data import data
13
+ from ivoryos.routes.library.library import library
14
+ from ivoryos.routes.design.design import design
15
+ from ivoryos.routes.execute.execute import execute
16
+ from ivoryos.routes.api.api import api
17
+ from ivoryos.socket_handlers import socketio
18
+ from ivoryos.routes.main.main import main
19
+ from ivoryos.version import __version__ as ivoryos_version
20
+
21
+ def create_app(config_class=None):
22
+ """
23
+ create app, init database
24
+ """
25
+
26
+ url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
27
+ app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
28
+ app.register_blueprint(main, url_prefix=url_prefix)
29
+ app.register_blueprint(auth, url_prefix=f'{url_prefix}/{auth.name}')
30
+ app.register_blueprint(library, url_prefix=f'{url_prefix}/{library.name}')
31
+ app.register_blueprint(control, url_prefix=f'{url_prefix}/instruments')
32
+ app.register_blueprint(design, url_prefix=f'{url_prefix}')
33
+ app.register_blueprint(execute, url_prefix=f'{url_prefix}')
34
+ app.register_blueprint(data, url_prefix=f'{url_prefix}')
35
+ app.register_blueprint(api, url_prefix=f'{url_prefix}/{api.name}')
36
+
37
+
38
+ app.config.from_object(config_class or 'config.get_config()')
39
+ os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
40
+ # Initialize extensions
41
+ socketio.init_app(app, cors_allowed_origins="*", cookie=None)
42
+ login_manager.init_app(app)
43
+ login_manager.login_view = "auth.login"
44
+ db.init_app(app)
45
+
46
+ # Create database tables
47
+ with app.app_context():
48
+ db.create_all()
49
+
50
+ # Additional setup
51
+ utils.create_gui_dir(app.config['OUTPUT_FOLDER'])
52
+
53
+ # logger_list = app.config["LOGGERS"]
54
+ logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
55
+ logger = utils.start_logger(socketio, 'gui_logger', logger_path)
56
+
57
+ @app.before_request
58
+ def before_request():
59
+ """
60
+ Called before
61
+
62
+ """
63
+ g.logger = logger
64
+ g.socketio = socketio
65
+ session.permanent = False
66
+ # DEMO_MODE: Simulate logged-in user per session
67
+ if app.config.get("DEMO_MODE", False):
68
+ if "demo_user_id" not in session:
69
+ session["demo_user_id"] = f"demo_{str(uuid.uuid4())[:8]}"
70
+
71
+ class SessionDemoUser(AnonymousUserMixin):
72
+ @property
73
+ def is_authenticated(self):
74
+ return True
75
+
76
+ def get_id(self):
77
+ return session.get("demo_user_id")
78
+
79
+ login_manager.anonymous_user = SessionDemoUser
80
+
81
+
82
+
83
+ @app.route('/')
84
+ def redirect_to_prefix():
85
+ return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
86
+
87
+ @app.template_filter('format_name')
88
+ def format_name(name):
89
+ name = name.split(".")[-1]
90
+ text = ' '.join(word for word in name.split('_'))
91
+ return text.capitalize()
92
+
93
+ # app.config.setdefault("DEMO_MODE", False)
94
+ return app
@@ -5,7 +5,7 @@ from ivoryos.routes.control.control_file import control_file
5
5
  from ivoryos.routes.control.control_new_device import control_temp
6
6
  from ivoryos.routes.control.utils import post_session_by_instrument, get_session_by_instrument, find_instrument_by_name
7
7
  from ivoryos.utils.global_config import GlobalConfig
8
- from ivoryos.utils.form import create_form_from_module
8
+ from ivoryos.utils.form import create_form_from_module, create_form_from_pseudo
9
9
  from ivoryos.utils.task_runner import TaskRunner
10
10
 
11
11
  global_config = GlobalConfig()
@@ -48,7 +48,10 @@ def deck_controllers(instrument: str = None):
48
48
  forms = None
49
49
  if instrument:
50
50
  inst_object = find_instrument_by_name(instrument)
51
- forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
51
+ if instrument.startswith("blocks"):
52
+ forms = create_form_from_pseudo(pseudo=inst_object, autofill=False, design=False)
53
+ else:
54
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
52
55
  order = get_session_by_instrument('card_order', instrument)
53
56
  hidden_functions = get_session_by_instrument('hidden_functions', instrument)
54
57
  functions = list(forms.keys())
@@ -99,12 +102,11 @@ def deck_controllers(instrument: str = None):
99
102
  function_data["signature"] = str(function_data["signature"])
100
103
  return jsonify(snapshot)
101
104
 
102
- deck_variables = global_config.deck_snapshot.keys()
103
- temp_variables = global_config.defined_variables.keys()
104
105
  return render_template(
105
106
  "controllers.html",
106
- defined_variables=deck_variables,
107
- temp_variables=temp_variables,
107
+ defined_variables=global_config.deck_snapshot.keys(),
108
+ block_variables=global_config.building_blocks.keys(),
109
+ temp_variables=global_config.defined_variables.keys(),
108
110
  instrument=instrument,
109
111
  forms=forms,
110
112
  session=session
@@ -49,6 +49,24 @@
49
49
  </div>
50
50
  </div>
51
51
  {% endif %}
52
+
53
+ {% if block_variables %}
54
+ <div class="mb-4">
55
+ <h6 class="fw-bold text-secondary mb-2" style="letter-spacing: 1px;">Methods</h6>
56
+ <div class="list-group">
57
+ {% for inst in block_variables %}
58
+ <a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-warning text-dark border-0{% else %}bg-light{% endif %}"
59
+ href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
60
+ style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
61
+ <span class="flex-grow-1">{{ inst | format_name }}</span>
62
+ {% if instrument == inst %}
63
+ <span class="ms-auto">&gt;</span>
64
+ {% endif %}
65
+ </a>
66
+ {% endfor %}
67
+ </div>
68
+ </div>
69
+ {% endif %}
52
70
  <!-- Action Buttons -->
53
71
  <div class="mb-4">
54
72
  <a href="{{ url_for('control.file.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary w-100 mb-2">
@@ -13,6 +13,8 @@ def find_instrument_by_name(name: str):
13
13
  if name.startswith("deck"):
14
14
  name = name.replace("deck.", "")
15
15
  return getattr(global_config.deck, name)
16
+ elif name.startswith("blocks"):
17
+ return global_config.building_blocks[name]
16
18
  elif name in global_config.defined_variables:
17
19
  return global_config.defined_variables[name]
18
20
  elif name in globals():
@@ -35,6 +35,9 @@ def _create_forms(instrument, script, autofill, pseudo_deck = None):
35
35
  _object = global_config.defined_variables.get(instrument)
36
36
  functions = utils._inspect_class(_object)
37
37
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
38
+ elif instrument.startswith("blocks"):
39
+ forms = create_form_from_pseudo(pseudo=global_config.building_blocks[instrument], autofill=autofill, script=script)
40
+ functions = global_config.building_blocks[instrument]
38
41
  else:
39
42
  if deck:
40
43
  functions = global_config.deck_snapshot.get(instrument, {})
@@ -92,7 +95,7 @@ def experiment_builder():
92
95
 
93
96
  return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
94
97
  script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
95
- local_variables=global_config.defined_variables)
98
+ local_variables=global_config.defined_variables, block_variables=global_config.building_blocks)
96
99
 
97
100
 
98
101
  @design.route("/draft/meta", methods=["PATCH"])
@@ -192,7 +195,8 @@ def update_ui_state():
192
195
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
193
196
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
194
197
  html = render_template("components/sidebar.html", history=deck_list,
195
- defined_variables=deck_variables, local_variables = global_config.defined_variables)
198
+ defined_variables=deck_variables, local_variables = global_config.defined_variables,
199
+ block_variables=global_config.building_blocks)
196
200
  return jsonify({"html": html})
197
201
  return jsonify({"error": "Invalid request"}), 400
198
202
 
@@ -310,6 +314,7 @@ def methods_handler(instrument: str = ''):
310
314
 
311
315
  success = True
312
316
  msg = ""
317
+ request.form
313
318
  if "hidden_name" in request.form:
314
319
  method_name = request.form.get("hidden_name", None)
315
320
  form = forms.get(method_name) if forms else None
@@ -322,7 +327,7 @@ def methods_handler(instrument: str = ''):
322
327
  primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
323
328
 
324
329
  # todo
325
- print(primitive_arg_types)
330
+ # print(primitive_arg_types)
326
331
 
327
332
  script.eval_list(kwargs, primitive_arg_types)
328
333
  kwargs = script.validate_variables(kwargs)
@@ -422,7 +427,9 @@ def get_operation_sidebar(instrument: str = ''):
422
427
  # edit_action_info = session.get("edit_action")
423
428
  html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
424
429
  defined_variables=deck_variables,
425
- local_variables=global_config.defined_variables)
430
+ local_variables=global_config.defined_variables,
431
+ block_variables=global_config.building_blocks,
432
+ )
426
433
  return jsonify({"html": html})
427
434
 
428
435
 
@@ -1,6 +1,6 @@
1
1
  {# Action form component #}
2
- <div class="accordion-item design-control" draggable="true">
3
- <h2 class="accordion-header">
2
+ <div class="accordion-item design-control">
3
+ <h2 class="accordion-header" >
4
4
  <button class="accordion-button collapsed draggable-action"
5
5
  type="button" data-bs-toggle="collapse"
6
6
  data-bs-target="#{{name}}" aria-expanded="false"
@@ -39,7 +39,29 @@
39
39
  </ul>
40
40
  </div>
41
41
  </div>
42
-
42
+ {% if block_variables %}
43
+ <div class="accordion-item design-control">
44
+ <h5 class="accordion-header">
45
+ <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#block" role="button" aria-expanded="false" aria-controls="collapseExample">
46
+ Methods
47
+ </button>
48
+ </h5>
49
+ <div class="accordion-collapse collapse show" id="block">
50
+ <ul class="list-group">
51
+ {% for category in block_variables %}
52
+ <button class="list-group-item list-group-item-action"
53
+ type="button"
54
+ name="device"
55
+ value="{{category}}"
56
+ data-get-url="{{ url_for('design.get_operation_sidebar', instrument=category) }}"
57
+ onclick="updateInstrumentPanel(this)">
58
+ {{ category|format_name }}
59
+ </button>
60
+ {% endfor%}
61
+ </ul>
62
+ </div>
63
+ </div>
64
+ {% endif %}
43
65
  {% if local_variables %}
44
66
  <div class="accordion-item design-control">
45
67
  <h5 class="accordion-header">
@@ -1,37 +1,24 @@
1
1
  import os
2
+ import sqlite3
2
3
  import sys
3
- import uuid
4
4
  from typing import Union
5
5
 
6
- from flask import Flask, redirect, url_for, g, Blueprint, session
7
- from flask_login import AnonymousUserMixin
6
+ from flask import Blueprint
8
7
 
9
- # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8
+ from sqlalchemy import Engine, event
10
9
 
10
+ # from ivoryos import BUILDING_BLOCKS
11
+ from ivoryos.app import create_app
11
12
  from ivoryos.config import Config, get_config
12
- from ivoryos.routes.auth.auth import auth, login_manager
13
- from ivoryos.routes.control.control import control
14
- from ivoryos.routes.data.data import data
15
- from ivoryos.routes.library.library import library
16
- from ivoryos.routes.design.design import design
17
- from ivoryos.routes.execute.execute import execute
18
- from ivoryos.routes.api.api import api
13
+ from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
14
+ from ivoryos.routes.auth.auth import login_manager
15
+ from ivoryos.routes.control.control import global_config
19
16
  from ivoryos.socket_handlers import socketio
20
- from ivoryos.routes.main.main import main
21
- # from ivoryos.routes.monitor.monitor import monitor
22
17
  from ivoryos.utils import utils
23
18
  from ivoryos.utils.db_models import db, User
24
- from ivoryos.utils.global_config import GlobalConfig
25
- from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
26
- from ivoryos.utils.script_runner import ScriptRunner
27
- from ivoryos.version import __version__ as ivoryos_version
28
- # from importlib.metadata import entry_points
29
19
 
30
- global_config = GlobalConfig()
31
- from sqlalchemy import event
32
- from sqlalchemy.engine import Engine
33
- import sqlite3
34
20
 
21
+ url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
35
22
 
36
23
  @event.listens_for(Engine, "connect")
37
24
  def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
@@ -41,16 +28,6 @@ def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
41
28
  cursor.close()
42
29
 
43
30
 
44
- url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
45
- app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
46
- app.register_blueprint(main, url_prefix=url_prefix)
47
- app.register_blueprint(auth, url_prefix=f'{url_prefix}/{auth.name}')
48
- app.register_blueprint(library, url_prefix=f'{url_prefix}/{library.name}')
49
- app.register_blueprint(control, url_prefix=f'{url_prefix}/instruments')
50
- app.register_blueprint(design, url_prefix=f'{url_prefix}')
51
- app.register_blueprint(execute, url_prefix=f'{url_prefix}')
52
- app.register_blueprint(data, url_prefix=f'{url_prefix}')
53
- app.register_blueprint(api, url_prefix=f'{url_prefix}/{api.name}')
54
31
 
55
32
  @login_manager.user_loader
56
33
  def load_user(user_id):
@@ -62,68 +39,8 @@ def load_user(user_id):
62
39
  return db.session.get(User, user_id)
63
40
 
64
41
 
65
- def create_app(config_class=None):
66
- """
67
- create app, init database
68
- """
69
- app.config.from_object(config_class or 'config.get_config()')
70
- os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
71
- # Initialize extensions
72
- socketio.init_app(app, cors_allowed_origins="*", cookie=None)
73
- login_manager.init_app(app)
74
- login_manager.login_view = "auth.login"
75
- db.init_app(app)
76
-
77
- # Create database tables
78
- with app.app_context():
79
- db.create_all()
80
-
81
- # Additional setup
82
- utils.create_gui_dir(app.config['OUTPUT_FOLDER'])
83
-
84
- # logger_list = app.config["LOGGERS"]
85
- logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
86
- logger = utils.start_logger(socketio, 'gui_logger', logger_path)
87
-
88
- @app.before_request
89
- def before_request():
90
- """
91
- Called before
92
-
93
- """
94
- g.logger = logger
95
- g.socketio = socketio
96
- session.permanent = False
97
- # DEMO_MODE: Simulate logged-in user per session
98
- if app.config.get("DEMO_MODE", False):
99
- if "demo_user_id" not in session:
100
- session["demo_user_id"] = f"demo_{str(uuid.uuid4())[:8]}"
101
-
102
- class SessionDemoUser(AnonymousUserMixin):
103
- @property
104
- def is_authenticated(self):
105
- return True
106
-
107
- def get_id(self):
108
- return session.get("demo_user_id")
109
-
110
- login_manager.anonymous_user = SessionDemoUser
111
-
112
42
 
113
43
 
114
- @app.route('/')
115
- def redirect_to_prefix():
116
- return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
117
-
118
- @app.template_filter('format_name')
119
- def format_name(name):
120
- name = name.split(".")[-1]
121
- text = ' '.join(word for word in name.split('_'))
122
- return text.capitalize()
123
-
124
- # app.config.setdefault("DEMO_MODE", False)
125
- return app
126
-
127
44
 
128
45
  def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, model=None,
129
46
  config: Config = None,
@@ -182,6 +99,8 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
182
99
  save=True,
183
100
  exclude_names=exclude_names
184
101
  )
102
+ global_config.building_blocks = utils.create_block_snapshot()
103
+
185
104
  else:
186
105
  app.config["OFF_LINE"] = True
187
106
  if model:
@@ -200,12 +119,12 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
200
119
  for log in logger:
201
120
  utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
202
121
 
203
- # in case Python 3.12 or higher doesn't log URL
204
- if sys.version_info >= (3, 12):
205
- ip = utils.get_local_ip()
206
- print(f"Server running at http://localhost:{port}")
207
- if not ip == "127.0.0.1":
208
- print(f"Server running at http://{ip}:{port}")
122
+ # TODO in case Python 3.12 or higher doesn't log URL
123
+ # if sys.version_info >= (3, 12):
124
+ # ip = utils.get_local_ip()
125
+ # print(f"Server running at http://localhost:{port}")
126
+ # if not ip == "127.0.0.1":
127
+ # print(f"Server running at http://{ip}:{port}")
209
128
  socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
210
129
  # return app
211
130
 
@@ -246,3 +165,4 @@ def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
246
165
  plugin_names.append(blueprint.name)
247
166
  app.register_blueprint(blueprint, url_prefix=f"{url_prefix}/{blueprint.name}")
248
167
  return plugin_names
168
+
@@ -37,13 +37,43 @@ document.addEventListener("DOMContentLoaded", function() {
37
37
  console.error("Error received:", errorData);
38
38
  var progressBar = document.getElementById('progress-bar-inner');
39
39
 
40
- progressBar.classList.remove('bg-success');
41
- progressBar.classList.add('bg-danger'); // Red color for error
42
- // Show error modal
40
+ progressBar.classList.remove('bg-success', 'bg-warning');
41
+ progressBar.classList.add('bg-danger');
42
+
43
43
  var errorModal = new bootstrap.Modal(document.getElementById('error-modal'));
44
- document.getElementById('error-message').innerText = "An error occurred: " + errorData.message;
44
+ document.getElementById('errorModalLabel').innerText = "Error Detected";
45
+ document.getElementById('error-message').innerText =
46
+ "An error occurred: " + errorData.message;
47
+
48
+ // Show all buttons again
49
+ document.getElementById('retry-btn').style.display = "inline-block";
50
+ document.getElementById('continue-btn').style.display = "inline-block";
51
+ document.getElementById('stop-btn').style.display = "inline-block";
52
+
45
53
  errorModal.show();
54
+ });
55
+
56
+
57
+ socket.on('human_intervention', function(data) {
58
+ console.warn("Human intervention required:", data);
59
+ var progressBar = document.getElementById('progress-bar-inner');
46
60
 
61
+ // Set progress bar to yellow
62
+ progressBar.classList.remove('bg-success', 'bg-danger');
63
+ progressBar.classList.add('bg-warning');
64
+
65
+ // Reuse error modal but update content
66
+ var errorModal = new bootstrap.Modal(document.getElementById('error-modal'));
67
+ document.getElementById('errorModalLabel').innerText = "Human Intervention Required";
68
+ document.getElementById('error-message').innerText =
69
+ "Workflow paused: " + (data.message || "Please check and manually resume.");
70
+
71
+ // Optionally: hide retry button, since it may not apply
72
+ document.getElementById('retry-btn').style.display = "none";
73
+ document.getElementById('continue-btn').style.display = "inline-block";
74
+ document.getElementById('stop-btn').style.display = "inline-block";
75
+
76
+ errorModal.show();
47
77
  });
48
78
 
49
79
  // Handle Pause/Resume Button
@@ -71,6 +101,11 @@ document.addEventListener("DOMContentLoaded", function() {
71
101
  document.getElementById('continue-btn').addEventListener('click', function() {
72
102
  socket.emit('pause'); // Resume execution
73
103
  console.log("Execution resumed.");
104
+
105
+ // Reset progress bar color to running (blue)
106
+ var progressBar = document.getElementById('progress-bar-inner');
107
+ progressBar.classList.remove('bg-danger', 'bg-warning');
108
+ progressBar.classList.add('bg-primary');
74
109
  });
75
110
 
76
111
  document.getElementById('retry-btn').addEventListener('click', function() {
@@ -115,20 +115,37 @@ function insertDropPlaceholder($target) {
115
115
 
116
116
  // Add this function to sortable_design.js
117
117
  function initializeDragHandlers() {
118
- $(".accordion-item").off("dragstart").on("dragstart", function (event) {
119
- let formHtml = $(this).find(".accordion-body form").prop('outerHTML');
120
-
121
- if (!formHtml) {
122
- console.error("Form not found in accordion-body");
123
- return false;
124
- }
118
+ const $cards = $(".accordion-item.design-control");
125
119
 
126
- event.originalEvent.dataTransfer.setData("form", formHtml);
127
- event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
128
- event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
120
+ // Toggle draggable based on mouse/touch position
121
+ $cards.off("mousedown touchstart").on("mousedown touchstart", function (event) {
122
+ this.setAttribute("draggable", $(event.target).closest(".input-group").length ? "false" : "true");
123
+ });
129
124
 
130
- $(this).addClass("dragging");
125
+ // Handle the actual drag
126
+ $cards.off("dragstart dragend").on({
127
+ dragstart: function (event) {
128
+ if (this.getAttribute("draggable") !== "true") {
129
+ event.preventDefault();
130
+ return false;
131
+ }
132
+
133
+ const formHtml = $(this).find(".accordion-body form").prop("outerHTML");
134
+ if (!formHtml) return false;
135
+
136
+ event.originalEvent.dataTransfer.setData("form", formHtml);
137
+ event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
138
+ event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
139
+
140
+ $(this).addClass("dragging");
141
+ },
142
+ dragend: function () {
143
+ $(this).removeClass("dragging").attr("draggable", "false");
144
+ }
131
145
  });
146
+
147
+ // Prevent form inputs from being draggable
148
+ $(".accordion-item input, .accordion-item select").attr("draggable", "false");
132
149
  }
133
150
 
134
151
  // Make sure it's called in the document ready function
@@ -318,6 +318,12 @@ class Script(db.Model):
318
318
  {"id": current_len + 2, "instrument": 'repeat', "action": 'endrepeat',
319
319
  "args": {}, "return": '', "uuid": uid},
320
320
  ],
321
+ "pause":
322
+ [
323
+ {"id": current_len + 1, "instrument": 'pause', "action": "pause",
324
+ "args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid,
325
+ "arg_types": {"statement": "str"}}
326
+ ],
321
327
  }
322
328
  action_list = logic_dict[logic_type]
323
329
  self.currently_editing_script.extend(action_list)
@@ -443,6 +449,9 @@ class Script(db.Model):
443
449
  Compile the current script to a Python file.
444
450
  :return: String to write to a Python file.
445
451
  """
452
+ self.needs_call_human = False
453
+ self.blocks_included = False
454
+
446
455
  self.sort_actions()
447
456
  run_name = self.name if self.name else "untitled"
448
457
  run_name = self.validate_function_name(run_name)
@@ -524,6 +533,9 @@ class Script(db.Model):
524
533
  return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
525
534
  elif instrument == 'repeat':
526
535
  return self._process_repeat(indent_unit, action_name, statement, next_action)
536
+ elif instrument == 'pause':
537
+ self.needs_call_human = True
538
+ return f"{self.indent(indent_unit)}pause('{statement}')", indent_unit
527
539
  #todo
528
540
  # elif instrument == 'registered_workflows':
529
541
  # return inspect.getsource(my_function)
@@ -592,14 +604,18 @@ class Script(db.Model):
592
604
  """
593
605
  Process actions related to instruments.
594
606
  """
607
+ function_call = f"{instrument}.{action}"
608
+ if instrument.startswith("blocks"):
609
+ self.blocks_included = True
610
+ function_call = action
595
611
 
596
- if isinstance(args, dict):
612
+ if isinstance(args, dict) and args != {}:
597
613
  args_str = self._process_dict_args(args)
598
- single_line = f"{instrument}.{action}(**{args_str})"
614
+ single_line = f"{function_call}(**{args_str})"
599
615
  elif isinstance(args, str):
600
- single_line = f"{instrument}.{action} = {args}"
616
+ single_line = f"{function_call} = {args}"
601
617
  else:
602
- single_line = f"{instrument}.{action}()"
618
+ single_line = f"{function_call}()"
603
619
 
604
620
  if save_data:
605
621
  save_data += " = "
@@ -640,7 +656,7 @@ class Script(db.Model):
640
656
  """
641
657
  return arg in self.script_dict and self.script_dict[arg].get("arg_types") == "variable"
642
658
 
643
- def _write_to_file(self, script_path, run_name, exec_string):
659
+ def _write_to_file(self, script_path, run_name, exec_string, call_human=False):
644
660
  """
645
661
  Write the compiled script to a file.
646
662
  """
@@ -650,9 +666,28 @@ class Script(db.Model):
650
666
  else:
651
667
  s.write("deck = None")
652
668
  s.write("\nimport time")
669
+ if self.blocks_included:
670
+ s.write(f"\n{self._create_block_import()}")
671
+ if self.needs_call_human:
672
+ s.write("""\n\ndef pause(reason="Manual intervention required"):\n\tprint(f"\\nHUMAN INTERVENTION REQUIRED: {reason}")\n\tinput("Press Enter to continue...\\n")""")
673
+
653
674
  for i in exec_string.values():
654
675
  s.write(f"\n\n\n{i}")
655
676
 
677
+ def _create_block_import(self):
678
+ imports = {}
679
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
680
+ for category, methods in BUILDING_BLOCKS.items():
681
+ for method_name, meta in methods.items():
682
+ func = meta["func"]
683
+ module = meta["path"]
684
+ name = func.__name__
685
+ imports.setdefault(module, set()).add(name)
686
+ lines = []
687
+ for module, funcs in imports.items():
688
+ lines.append(f"from {module} import {', '.join(sorted(funcs))}")
689
+ return "\n".join(lines)
690
+
656
691
  class WorkflowRun(db.Model):
657
692
  __tablename__ = 'workflow_runs'
658
693