ivoryos 1.2.6a0__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 (111) hide show
  1. {ivoryos-1.2.6a0 → 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.6a0 → ivoryos-1.2.8}/ivoryos/routes/api/api.py +1 -1
  5. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/control.py +54 -28
  6. ivoryos-1.2.8/ivoryos/routes/control/control_file.py +33 -0
  7. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/templates/controllers.html +18 -0
  8. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/utils.py +2 -0
  9. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/design.py +11 -4
  10. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/action_form.html +2 -2
  11. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
  12. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/experiment_run.html +1 -6
  13. ivoryos-1.2.6a0/ivoryos/__init__.py → ivoryos-1.2.8/ivoryos/server.py +18 -98
  14. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/action_handlers.js +1 -1
  15. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/socket_handler.js +39 -4
  16. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/sortable_design.js +28 -11
  17. ivoryos-1.2.8/ivoryos/utils/client_proxy.py +288 -0
  18. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/db_models.py +40 -5
  19. ivoryos-1.2.8/ivoryos/utils/decorators.py +33 -0
  20. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/form.py +9 -2
  21. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/global_config.py +10 -0
  22. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/script_runner.py +21 -2
  23. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/task_runner.py +10 -5
  24. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/utils.py +20 -1
  25. ivoryos-1.2.8/ivoryos/version.py +1 -0
  26. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos.egg-info/PKG-INFO +1 -1
  27. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos.egg-info/SOURCES.txt +3 -0
  28. ivoryos-1.2.6a0/ivoryos/routes/control/control_file.py +0 -36
  29. ivoryos-1.2.6a0/ivoryos/utils/client_proxy.py +0 -57
  30. ivoryos-1.2.6a0/ivoryos/version.py +0 -1
  31. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/LICENSE +0 -0
  32. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/README.md +0 -0
  33. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/config.py +0 -0
  34. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/optimizer/ax_optimizer.py +0 -0
  35. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/optimizer/base_optimizer.py +0 -0
  36. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/optimizer/baybe_optimizer.py +0 -0
  37. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/optimizer/registry.py +0 -0
  38. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/__init__.py +0 -0
  39. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/auth/__init__.py +0 -0
  40. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/auth/auth.py +0 -0
  41. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/auth/templates/login.html +0 -0
  42. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/auth/templates/signup.html +0 -0
  43. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/__init__.py +0 -0
  44. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/control_new_device.py +0 -0
  45. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/control/templates/controllers_new.html +0 -0
  46. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/data/__init__.py +0 -0
  47. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/data/data.py +0 -0
  48. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/data/templates/components/step_card.html +0 -0
  49. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/data/templates/workflow_database.html +0 -0
  50. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/data/templates/workflow_view.html +0 -0
  51. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/__init__.py +0 -0
  52. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/design_file.py +0 -0
  53. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/design_step.py +0 -0
  54. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
  55. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
  56. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas.html +0 -0
  57. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
  58. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
  59. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
  60. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
  61. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
  62. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
  63. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
  64. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
  65. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
  66. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
  67. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/modals.html +0 -0
  68. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
  69. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
  70. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
  71. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
  72. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/__init__.py +0 -0
  73. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/execute.py +0 -0
  74. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/execute_file.py +0 -0
  75. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
  76. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
  77. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
  78. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
  79. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
  80. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
  81. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
  82. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
  83. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/library/__init__.py +0 -0
  84. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/library/library.py +0 -0
  85. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/library/templates/library.html +0 -0
  86. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/main/__init__.py +0 -0
  87. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/main/main.py +0 -0
  88. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/main/templates/help.html +0 -0
  89. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/routes/main/templates/home.html +0 -0
  90. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/socket_handlers.py +0 -0
  91. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/favicon.ico +0 -0
  92. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  93. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  94. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/db_delete.js +0 -0
  95. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/overlay.js +0 -0
  96. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/script_metadata.js +0 -0
  97. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/sortable_card.js +0 -0
  98. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/js/ui_state.js +0 -0
  99. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/logo.webp +0 -0
  100. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/static/style.css +0 -0
  101. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/templates/base.html +0 -0
  102. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/__init__.py +0 -0
  103. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/bo_campaign.py +0 -0
  104. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/llm_agent.py +0 -0
  105. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/py_to_json.py +0 -0
  106. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos/utils/serilize.py +0 -0
  107. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos.egg-info/dependency_links.txt +0 -0
  108. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos.egg-info/requires.txt +0 -0
  109. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/ivoryos.egg-info/top_level.txt +0 -0
  110. {ivoryos-1.2.6a0 → ivoryos-1.2.8}/pyproject.toml +0 -0
  111. {ivoryos-1.2.6a0 → 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.6a0
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
@@ -37,7 +37,7 @@ def backend_control(instrument: str=None):
37
37
  forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
38
38
 
39
39
  if request.method == 'POST':
40
- method_name = request.form.get("hidden_name", None)
40
+ method_name = request.json.get("hidden_name", None)
41
41
  form = forms.get(method_name, None)
42
42
  if form:
43
43
  kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
@@ -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()
@@ -21,7 +21,7 @@ control.register_blueprint(control_temp)
21
21
  @control.route("/", strict_slashes=False, methods=["GET", "POST"])
22
22
  @control.route("/<string:instrument>", strict_slashes=False, methods=["GET", "POST"])
23
23
  @login_required
24
- def deck_controllers():
24
+ def deck_controllers(instrument: str = None):
25
25
  """
26
26
  .. :quickref: Direct Control; device (instruments) and methods
27
27
 
@@ -44,43 +44,69 @@ def deck_controllers():
44
44
  :status 200: render template with instruments and methods
45
45
 
46
46
  """
47
- deck_variables = global_config.deck_snapshot.keys()
48
- temp_variables = global_config.defined_variables.keys()
49
- instrument = request.args.get('instrument')
47
+ instrument = instrument or request.args.get("instrument")
50
48
  forms = None
51
49
  if instrument:
52
50
  inst_object = find_instrument_by_name(instrument)
53
- _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)
54
55
  order = get_session_by_instrument('card_order', instrument)
55
56
  hidden_functions = get_session_by_instrument('hidden_functions', instrument)
56
- functions = list(_forms.keys())
57
+ functions = list(forms.keys())
57
58
  for function in functions:
58
59
  if function not in hidden_functions and function not in order:
59
60
  order.append(function)
60
61
  post_session_by_instrument('card_order', instrument, order)
61
- forms = {name: _forms[name] for name in order if name in _forms}
62
- # Handle POST for method execution
63
- if request.method == 'POST':
64
- all_kwargs = request.form.copy()
65
- method_name = all_kwargs.pop("hidden_name", None)
66
- form = forms.get(method_name)
67
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'} if form else {}
68
- if form and form.validate_on_submit():
69
- kwargs.pop("hidden_name", None)
70
- output = runner.run_single_step(instrument, method_name, kwargs, wait=True, current_app=current_app._get_current_object())
71
- if output["success"]:
72
- flash(f"\nRun Success! Output value: {output.get('output', 'None')}.")
73
- else:
74
- flash(f"\nRun Error! {output.get('output', 'Unknown error occurred.')}", "error")
62
+ forms = {name: forms[name] for name in order if name in forms}
63
+
64
+ if request.method == "POST":
65
+ if not forms:
66
+ return jsonify({"success": False, "error": "Instrument not found"}), 404
67
+
68
+ payload = request.get_json() if request.is_json else request.form.to_dict()
69
+ method_name = payload.pop("hidden_name", None)
70
+ form = forms.get(method_name)
71
+
72
+ if not form:
73
+ return jsonify({"success": False, "error": f"Method {method_name} not found"}), 404
74
+
75
+ # Extract kwargs
76
+ if request.is_json:
77
+ kwargs = {k: v for k, v in payload.items() if k not in ["csrf_token", "hidden_wait"]}
78
+ else:
79
+ kwargs = {field.name: field.data for field in form if field.name not in ["csrf_token", "hidden_name"]}
80
+
81
+ wait = str(payload.get("hidden_wait", "true")).lower() == "true"
82
+
83
+ output = runner.run_single_step(
84
+ component=instrument, method=method_name, kwargs=kwargs, wait=wait,
85
+ current_app=current_app._get_current_object()
86
+ )
87
+
88
+ if request.is_json:
89
+ return jsonify(output)
90
+ else:
91
+ if output.get("success"):
92
+ flash(f"Run Success! Output: {output.get('output', 'None')}")
75
93
  else:
76
- if form:
77
- flash(form.errors)
78
- else:
79
- flash("Invalid method selected.")
94
+ flash(f"Run Error! {output.get('output', 'Unknown error occurred.')}", "error")
95
+
96
+ # GET request → render web form or return snapshot for API
97
+ if request.is_json or request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
98
+
99
+ snapshot = global_config.deck_snapshot.copy()
100
+ for instrument_key, instrument_data in snapshot.items():
101
+ for function_key, function_data in instrument_data.items():
102
+ function_data["signature"] = str(function_data["signature"])
103
+ return jsonify(snapshot)
104
+
80
105
  return render_template(
81
- 'controllers.html',
82
- defined_variables=deck_variables,
83
- temp_variables=temp_variables,
106
+ "controllers.html",
107
+ defined_variables=global_config.deck_snapshot.keys(),
108
+ block_variables=global_config.building_blocks.keys(),
109
+ temp_variables=global_config.defined_variables.keys(),
84
110
  instrument=instrument,
85
111
  forms=forms,
86
112
  session=session
@@ -0,0 +1,33 @@
1
+ import os
2
+ from flask import Blueprint, request,current_app, send_file
3
+ from flask_login import login_required
4
+
5
+ from ivoryos.utils.client_proxy import ProxyGenerator
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+
8
+ global_config = GlobalConfig()
9
+
10
+ control_file = Blueprint('file', __name__)
11
+
12
+
13
+
14
+ @control_file.route("/files/proxy", strict_slashes=False)
15
+ @login_required
16
+ def download_proxy():
17
+ """
18
+ .. :quickref: Direct Control Files; Download proxy Python interface
19
+
20
+ download proxy Python interface
21
+
22
+ .. http:get:: /files/proxy
23
+ """
24
+ generator = ProxyGenerator(request.url_root)
25
+ snapshot = global_config.deck_snapshot.copy()
26
+
27
+ filepath = generator.generate_from_flask_route(
28
+ snapshot,
29
+ request.url_root,
30
+ current_app.config["OUTPUT_FOLDER"]
31
+ )
32
+
33
+ return send_file(os.path.abspath(filepath), as_attachment=True)
@@ -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">
@@ -15,12 +15,7 @@
15
15
  };
16
16
  </script>
17
17
  {% endif %}
18
- <div id="progress-warning" style="border: 1px solid #f5c2c7; background-color: #f8d7da; color: #842029; padding: 1rem; border-radius: 0.5rem; margin: 1rem 0; font-size: 0.95rem;">
19
- ⚠️ <strong>Note:</strong> Real-time progress tracking in this demo is currently <strong>not working</strong>. The system may report busy if multiple runs are initiated.
20
- <br>
21
- Workflow will still work, results can be tracked via the <strong>Data</strong> tab.
22
- <br>
23
- </div>
18
+
24
19
  <div class="row">
25
20
  {% include 'components/run_panel.html' %}
26
21
  {% include 'components/progress_panel.html' %}
@@ -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
+
@@ -15,7 +15,7 @@ function saveWorkflow(link) {
15
15
  .then(data => {
16
16
  if (data.success) {
17
17
  // flash a success message
18
- flash("Workflow saved successfully", "success");
18
+ // flash("Workflow saved successfully", "success");
19
19
  window.location.reload(); // or update the UI dynamically
20
20
  } else {
21
21
  alert("Failed to save workflow: " + data.error);
@@ -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() {