ivoryos 1.0.3__tar.gz → 1.0.5__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.
- {ivoryos-1.0.3/ivoryos.egg-info → ivoryos-1.0.5}/PKG-INFO +37 -1
- {ivoryos-1.0.3 → ivoryos-1.0.5}/README.md +36 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/__init__.py +23 -6
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/config.py +7 -1
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/auth/auth.py +12 -15
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/database.py +2 -2
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/design/design.py +22 -7
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/design/templates/design/experiment_builder.html +100 -45
- ivoryos-1.0.5/ivoryos/routes/design/templates/design/experiment_run.html +558 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/js/sortable_design.js +1 -1
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/form.py +30 -3
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/utils.py +13 -0
- ivoryos-1.0.5/ivoryos/version.py +1 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5/ivoryos.egg-info}/PKG-INFO +37 -1
- ivoryos-1.0.3/ivoryos/routes/design/templates/design/experiment_run.html +0 -395
- ivoryos-1.0.3/ivoryos/version.py +0 -1
- {ivoryos-1.0.3 → ivoryos-1.0.5}/LICENSE +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/MANIFEST.in +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/auth/templates/auth/login.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/control/control.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/control/templates/control/controllers.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/templates/database/scripts_database.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/templates/database/step_card.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/templates/database/workflow_database.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/database/templates/database/workflow_view.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/main/templates/main/help.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/main/templates/main/home.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/js/socket_handler.js +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/static/style.css +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/bo_campaign.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/client_proxy.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/db_models.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/global_config.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/script_runner.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/utils/task_runner.py +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos.egg-info/SOURCES.txt +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/setup.cfg +0 -0
- {ivoryos-1.0.3 → ivoryos-1.0.5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
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
|
|
@@ -168,6 +168,42 @@ When you run the application for the first time, it will automatically create th
|
|
|
168
168
|
- [x] show line number option ✅
|
|
169
169
|
- [ ] snapshot version control
|
|
170
170
|
|
|
171
|
+
## Citing
|
|
172
|
+
|
|
173
|
+
If you find this project useful, please consider citing the following manuscript:
|
|
174
|
+
|
|
175
|
+
> Zhang, W., Hao, L., Lai, V. et al. [IvoryOS: an interoperable web interface for orchestrating Python-based self-driving laboratories.](https://www.nature.com/articles/s41467-025-60514-w) Nat Commun 16, 5182 (2025).
|
|
176
|
+
|
|
177
|
+
```bibtex
|
|
178
|
+
@article{zhang_et_al_2025,
|
|
179
|
+
author = {Wenyu Zhang and Lucy Hao and Veronica Lai and Ryan Corkery and Jacob Jessiman and Jiayu Zhang and Junliang Liu and Yusuke Sato and Maria Politi and Matthew E. Reish and Rebekah Greenwood and Noah Depner and Jiyoon Min and Rama El-khawaldeh and Paloma Prieto and Ekaterina Trushina and Jason E. Hein},
|
|
180
|
+
title = {{IvoryOS}: an interoperable web interface for orchestrating {Python-based} self-driving laboratories},
|
|
181
|
+
journal = {Nature Communications},
|
|
182
|
+
year = {2025},
|
|
183
|
+
volume = {16},
|
|
184
|
+
number = {1},
|
|
185
|
+
pages = {5182},
|
|
186
|
+
doi = {10.1038/s41467-025-60514-w},
|
|
187
|
+
url = {https://doi.org/10.1038/s41467-025-60514-w}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
For an additional perspective related to the development of the tool, please see:
|
|
192
|
+
|
|
193
|
+
> Zhang, W., Hein, J. [Behind IvoryOS: Empowering Scientists to Harness Self-Driving Labs for Accelerated Discovery](https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery). Springer Nature Research Communities (2025).
|
|
194
|
+
|
|
195
|
+
```bibtex
|
|
196
|
+
@misc{zhang_hein_2025,
|
|
197
|
+
author = {Wenyu Zhang and Jason Hein},
|
|
198
|
+
title = {Behind {IvoryOS}: Empowering Scientists to Harness Self-Driving Labs for Accelerated Discovery},
|
|
199
|
+
howpublished = {Springer Nature Research Communities},
|
|
200
|
+
year = {2025},
|
|
201
|
+
month = {Jun},
|
|
202
|
+
day = {18},
|
|
203
|
+
url = {https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
171
207
|
## Authors and Acknowledgement
|
|
172
208
|
Ivory Zhang, Lucy Hao
|
|
173
209
|
|
|
@@ -156,6 +156,42 @@ When you run the application for the first time, it will automatically create th
|
|
|
156
156
|
- [x] show line number option ✅
|
|
157
157
|
- [ ] snapshot version control
|
|
158
158
|
|
|
159
|
+
## Citing
|
|
160
|
+
|
|
161
|
+
If you find this project useful, please consider citing the following manuscript:
|
|
162
|
+
|
|
163
|
+
> Zhang, W., Hao, L., Lai, V. et al. [IvoryOS: an interoperable web interface for orchestrating Python-based self-driving laboratories.](https://www.nature.com/articles/s41467-025-60514-w) Nat Commun 16, 5182 (2025).
|
|
164
|
+
|
|
165
|
+
```bibtex
|
|
166
|
+
@article{zhang_et_al_2025,
|
|
167
|
+
author = {Wenyu Zhang and Lucy Hao and Veronica Lai and Ryan Corkery and Jacob Jessiman and Jiayu Zhang and Junliang Liu and Yusuke Sato and Maria Politi and Matthew E. Reish and Rebekah Greenwood and Noah Depner and Jiyoon Min and Rama El-khawaldeh and Paloma Prieto and Ekaterina Trushina and Jason E. Hein},
|
|
168
|
+
title = {{IvoryOS}: an interoperable web interface for orchestrating {Python-based} self-driving laboratories},
|
|
169
|
+
journal = {Nature Communications},
|
|
170
|
+
year = {2025},
|
|
171
|
+
volume = {16},
|
|
172
|
+
number = {1},
|
|
173
|
+
pages = {5182},
|
|
174
|
+
doi = {10.1038/s41467-025-60514-w},
|
|
175
|
+
url = {https://doi.org/10.1038/s41467-025-60514-w}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For an additional perspective related to the development of the tool, please see:
|
|
180
|
+
|
|
181
|
+
> Zhang, W., Hein, J. [Behind IvoryOS: Empowering Scientists to Harness Self-Driving Labs for Accelerated Discovery](https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery). Springer Nature Research Communities (2025).
|
|
182
|
+
|
|
183
|
+
```bibtex
|
|
184
|
+
@misc{zhang_hein_2025,
|
|
185
|
+
author = {Wenyu Zhang and Jason Hein},
|
|
186
|
+
title = {Behind {IvoryOS}: Empowering Scientists to Harness Self-Driving Labs for Accelerated Discovery},
|
|
187
|
+
howpublished = {Springer Nature Research Communities},
|
|
188
|
+
year = {2025},
|
|
189
|
+
month = {Jun},
|
|
190
|
+
day = {18},
|
|
191
|
+
url = {https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
159
195
|
## Authors and Acknowledgement
|
|
160
196
|
Ivory Zhang, Lucy Hao
|
|
161
197
|
|
|
@@ -12,7 +12,7 @@ from ivoryos.routes.design.design import design, socketio
|
|
|
12
12
|
from ivoryos.routes.main.main import main
|
|
13
13
|
# from ivoryos.routes.monitor.monitor import monitor
|
|
14
14
|
from ivoryos.utils import utils
|
|
15
|
-
from ivoryos.utils.db_models import db
|
|
15
|
+
from ivoryos.utils.db_models import db, User
|
|
16
16
|
from ivoryos.utils.global_config import GlobalConfig
|
|
17
17
|
from ivoryos.utils.script_runner import ScriptRunner
|
|
18
18
|
from ivoryos.version import __version__ as ivoryos_version
|
|
@@ -40,6 +40,15 @@ app.register_blueprint(control, url_prefix=url_prefix)
|
|
|
40
40
|
app.register_blueprint(design, url_prefix=url_prefix)
|
|
41
41
|
app.register_blueprint(database, url_prefix=url_prefix)
|
|
42
42
|
|
|
43
|
+
@login_manager.user_loader
|
|
44
|
+
def load_user(user_id):
|
|
45
|
+
"""
|
|
46
|
+
This function is called by Flask-Login on every request to get the
|
|
47
|
+
current user object from the user ID stored in the session.
|
|
48
|
+
"""
|
|
49
|
+
# The correct implementation is to fetch the user from the database.
|
|
50
|
+
return db.session.get(User, user_id)
|
|
51
|
+
|
|
43
52
|
|
|
44
53
|
def create_app(config_class=None):
|
|
45
54
|
"""
|
|
@@ -85,8 +94,8 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
85
94
|
logger: Union[str, list] = None,
|
|
86
95
|
logger_output_name: str = None,
|
|
87
96
|
enable_design: bool = True,
|
|
88
|
-
blueprint_plugins: Union[list
|
|
89
|
-
exclude_names: list
|
|
97
|
+
blueprint_plugins: Union[list, Blueprint] = [],
|
|
98
|
+
exclude_names: list = [],
|
|
90
99
|
):
|
|
91
100
|
"""
|
|
92
101
|
Start ivoryOS app server.
|
|
@@ -101,10 +110,11 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
101
110
|
:param logger: logger name of list of logger names, defaults to None
|
|
102
111
|
:param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
|
|
103
112
|
:param enable_design: enable design canvas, database and workflow execution
|
|
104
|
-
:param blueprint_plugins: custom Blueprint pages
|
|
105
|
-
:param exclude_names: module names to exclude from parsing
|
|
113
|
+
:param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
|
|
114
|
+
:param exclude_names: list[str] module names to exclude from parsing
|
|
106
115
|
"""
|
|
107
116
|
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
117
|
+
os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
|
|
108
118
|
|
|
109
119
|
# plugins = load_installed_plugins(app, socketio)
|
|
110
120
|
plugins = []
|
|
@@ -153,6 +163,13 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
153
163
|
elif type(logger) is list:
|
|
154
164
|
for log in logger:
|
|
155
165
|
utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
|
|
166
|
+
|
|
167
|
+
# in case Python 3.12 or higher doesn't log URL
|
|
168
|
+
if sys.version_info >= (3, 12):
|
|
169
|
+
ip = utils.get_ip_address()
|
|
170
|
+
print(f"Server running at http://localhost:{port}")
|
|
171
|
+
if not ip == "127.0.0.1":
|
|
172
|
+
print(f"Server running at http://{ip}:{port}")
|
|
156
173
|
socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
|
|
157
174
|
# return app
|
|
158
175
|
|
|
@@ -175,7 +192,7 @@ def load_installed_plugins(app, socketio):
|
|
|
175
192
|
return plugin_names
|
|
176
193
|
|
|
177
194
|
|
|
178
|
-
def load_plugins(blueprints: Union[list
|
|
195
|
+
def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
|
|
179
196
|
"""
|
|
180
197
|
Dynamically load installed plugins and attach Flask-SocketIO.
|
|
181
198
|
"""
|
|
@@ -10,7 +10,6 @@ class Config:
|
|
|
10
10
|
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', None)
|
|
11
11
|
|
|
12
12
|
OUTPUT_FOLDER = os.path.join(os.path.abspath(os.curdir), 'ivoryos_data')
|
|
13
|
-
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
|
14
13
|
CSV_FOLDER = os.path.join(OUTPUT_FOLDER, 'config_csv/')
|
|
15
14
|
SCRIPT_FOLDER = os.path.join(OUTPUT_FOLDER, 'scripts/')
|
|
16
15
|
DATA_FOLDER = os.path.join(OUTPUT_FOLDER, 'results/')
|
|
@@ -38,10 +37,17 @@ class TestingConfig(Config):
|
|
|
38
37
|
DEBUG = True
|
|
39
38
|
TESTING = True
|
|
40
39
|
|
|
40
|
+
class DemoConfig(Config):
|
|
41
|
+
DEBUG = False
|
|
42
|
+
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
|
43
|
+
OUTPUT_FOLDER = "/tmp/ivoryos_data"
|
|
44
|
+
|
|
41
45
|
|
|
42
46
|
def get_config(env='dev'):
|
|
43
47
|
if env == 'production':
|
|
44
48
|
return ProductionConfig()
|
|
45
49
|
elif env == 'testing':
|
|
46
50
|
return TestingConfig()
|
|
51
|
+
elif env == 'demo':
|
|
52
|
+
return DemoConfig()
|
|
47
53
|
return DevelopmentConfig()
|
|
@@ -69,16 +69,18 @@ def signup():
|
|
|
69
69
|
if request.method == 'POST':
|
|
70
70
|
username = request.form.get('username')
|
|
71
71
|
password = request.form.get('password')
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return redirect(url_for('auth.login'))
|
|
79
|
-
except Exception:
|
|
80
|
-
flash("username exists :(", "error")
|
|
72
|
+
|
|
73
|
+
# Query the database to see if the user already exists.
|
|
74
|
+
existing_user = User.query.filter_by(username=username).first()
|
|
75
|
+
|
|
76
|
+
if existing_user:
|
|
77
|
+
flash("User already exists :(", "error")
|
|
81
78
|
return render_template('signup.html'), 409
|
|
79
|
+
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
|
80
|
+
user = User(username, hashed)
|
|
81
|
+
db.session.add(user)
|
|
82
|
+
db.session.commit()
|
|
83
|
+
return redirect(url_for('auth.login'))
|
|
82
84
|
return render_template('signup.html')
|
|
83
85
|
|
|
84
86
|
|
|
@@ -92,9 +94,4 @@ def logout():
|
|
|
92
94
|
"""
|
|
93
95
|
logout_user()
|
|
94
96
|
session.clear()
|
|
95
|
-
return redirect(url_for('auth.login'))
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@login_manager.user_loader
|
|
99
|
-
def load_user(username):
|
|
100
|
-
return User(username, password=None)
|
|
97
|
+
return redirect(url_for('auth.login'))
|
|
@@ -20,7 +20,7 @@ def edit_workflow(script_name:str):
|
|
|
20
20
|
|
|
21
21
|
:param script_name: script name
|
|
22
22
|
:type script_name: str
|
|
23
|
-
:status 302: redirect to :http:get:`/ivoryos/
|
|
23
|
+
:status 302: redirect to :http:get:`/ivoryos/design/script/`
|
|
24
24
|
"""
|
|
25
25
|
row = Script.query.get(script_name)
|
|
26
26
|
script = Script(**row.as_dict())
|
|
@@ -244,7 +244,7 @@ def get_workflow_steps(workflow_id:int):
|
|
|
244
244
|
.. http:get:: /database/workflows/<int:workflow_id>
|
|
245
245
|
|
|
246
246
|
"""
|
|
247
|
-
workflow =
|
|
247
|
+
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
248
248
|
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
249
249
|
|
|
250
250
|
# Use full objects for template rendering
|
|
@@ -120,7 +120,7 @@ def experiment_builder(instrument=None):
|
|
|
120
120
|
if deck and script.deck is None:
|
|
121
121
|
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
122
122
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
123
|
-
script.sort_actions()
|
|
123
|
+
# script.sort_actions()
|
|
124
124
|
|
|
125
125
|
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
126
126
|
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
@@ -202,11 +202,11 @@ def experiment_builder(instrument=None):
|
|
|
202
202
|
logic_type = kwargs.pop('builtin_name')
|
|
203
203
|
if 'variable' in kwargs:
|
|
204
204
|
try:
|
|
205
|
-
script.add_variable(
|
|
205
|
+
script.add_variable(insert_position=insert_position, **kwargs)
|
|
206
206
|
except ValueError:
|
|
207
207
|
flash("Invalid variable type")
|
|
208
208
|
else:
|
|
209
|
-
script.add_logic_action(logic_type=logic_type,
|
|
209
|
+
script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
|
|
210
210
|
else:
|
|
211
211
|
flash(form.errors)
|
|
212
212
|
elif request.method == 'POST' and "workflow_name" in request.form:
|
|
@@ -240,6 +240,10 @@ def experiment_builder(instrument=None):
|
|
|
240
240
|
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
241
241
|
|
|
242
242
|
utils.post_script_file(script)
|
|
243
|
+
|
|
244
|
+
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
245
|
+
session['python_code'] = exec_string
|
|
246
|
+
|
|
243
247
|
design_buttons = create_action_button(script)
|
|
244
248
|
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
245
249
|
script=script, defined_variables=deck_variables,
|
|
@@ -313,7 +317,8 @@ def experiment_run():
|
|
|
313
317
|
"""
|
|
314
318
|
deck = global_config.deck
|
|
315
319
|
script = utils.get_script_file()
|
|
316
|
-
|
|
320
|
+
|
|
321
|
+
# script.sort_actions() # handled in update list
|
|
317
322
|
off_line = current_app.config["OFF_LINE"]
|
|
318
323
|
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
319
324
|
# if not off_line and deck is None:
|
|
@@ -345,7 +350,7 @@ def experiment_run():
|
|
|
345
350
|
if filename:
|
|
346
351
|
# config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
347
352
|
config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
348
|
-
config_preview = config[1:
|
|
353
|
+
config_preview = config[1:]
|
|
349
354
|
arg_type = config.pop(0) # first entry is types
|
|
350
355
|
try:
|
|
351
356
|
for key, func_str in exec_string.items():
|
|
@@ -439,14 +444,24 @@ def toggle_script_type(stype=None):
|
|
|
439
444
|
return redirect(url_for('design.experiment_builder'))
|
|
440
445
|
|
|
441
446
|
|
|
442
|
-
@design.route("/updateList", methods=['
|
|
447
|
+
@design.route("/updateList", methods=['POST'])
|
|
443
448
|
@login_required
|
|
444
449
|
def update_list():
|
|
445
450
|
order = request.form['order']
|
|
446
451
|
script = utils.get_script_file()
|
|
447
452
|
script.currently_editing_order = order.split(",", len(script.currently_editing_script))
|
|
453
|
+
script.sort_actions()
|
|
454
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
448
455
|
utils.post_script_file(script)
|
|
449
|
-
|
|
456
|
+
session['python_code'] = exec_string
|
|
457
|
+
|
|
458
|
+
return jsonify({'success': True})
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@design.route("/toggle_show_code", methods=["POST"])
|
|
462
|
+
def toggle_show_code():
|
|
463
|
+
session["show_code"] = not session.get("show_code", False)
|
|
464
|
+
return redirect(request.referrer or url_for("design.experiment_builder"))
|
|
450
465
|
|
|
451
466
|
|
|
452
467
|
# --------------------handle all the import/export and download/upload--------------------------
|
{ivoryos-1.0.3 → ivoryos-1.0.5}/ivoryos/routes/design/templates/design/experiment_builder.html
RENAMED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
|
|
4
4
|
{% block body %}
|
|
5
5
|
{# overlay block for text-to-code gen #}
|
|
6
|
+
<style>
|
|
7
|
+
.code-overlay {
|
|
8
|
+
position: absolute;
|
|
9
|
+
top: 0;
|
|
10
|
+
right: -50%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
width: 50%;
|
|
13
|
+
z-index: 100;
|
|
14
|
+
background: #f8f9fa;
|
|
15
|
+
box-shadow: -2px 0 6px rgba(0, 0, 0, 0.1);
|
|
16
|
+
transition: right 0.3s ease;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
}
|
|
19
|
+
.code-overlay.show {
|
|
20
|
+
right: 0;
|
|
21
|
+
}
|
|
22
|
+
</style>
|
|
6
23
|
<div id="overlay" class="overlay">
|
|
7
24
|
<div>
|
|
8
25
|
<h3 id="overlay-text">Generating design, please wait...</h3>
|
|
@@ -252,6 +269,13 @@
|
|
|
252
269
|
<li class="nav-item"><a class="{{'nav-link active' if script.editing_type=='script' else 'nav-link'}}" aria-current="page" href="{{url_for('design.toggle_script_type', stype='script') }}">Experiment</a></li>
|
|
253
270
|
<li class="nav-item"><a class="{{'nav-link active' if script.editing_type=='cleanup' else 'nav-link'}}" aria-current="page" href="{{url_for('design.toggle_script_type', stype='cleanup') }}">Clean up</a></li>
|
|
254
271
|
</ul>
|
|
272
|
+
<form method="POST" action="{{ url_for('design.toggle_show_code') }}" class="ms-3">
|
|
273
|
+
<div class="form-check form-switch">
|
|
274
|
+
<input class="form-check-input" type="checkbox" id="showPythonCodeSwitch" name="show_code"
|
|
275
|
+
onchange="this.form.submit()" {% if session.get('show_code') %}checked{% endif %}>
|
|
276
|
+
<label class="form-check-label" for="showPythonCodeSwitch">Show Python Code</label>
|
|
277
|
+
</div>
|
|
278
|
+
</form>
|
|
255
279
|
|
|
256
280
|
<div class="form-check form-switch ms-auto">
|
|
257
281
|
<input class="form-check-input" type="checkbox" id="toggleLineNumbers" onchange="toggleLineNumbers()">
|
|
@@ -259,54 +283,74 @@
|
|
|
259
283
|
</div>
|
|
260
284
|
|
|
261
285
|
</div>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{% endfor %}
|
|
279
|
-
</td>
|
|
280
|
-
</tr>
|
|
281
|
-
<tr>
|
|
282
|
-
<th scope="row">Config Variables <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This shows variables you want to configure later using .csv file"><i class="bi bi-info-circle"></i></a></th>
|
|
283
|
-
<td>
|
|
284
|
-
<ul>
|
|
285
|
-
{% for i in script.config("script")[0] %}
|
|
286
|
-
<li>{{i}}</li>
|
|
286
|
+
<div class="canvas-wrapper position-relative">
|
|
287
|
+
<div class="canvas" droppable="true">
|
|
288
|
+
<div class="collapse" id="info">
|
|
289
|
+
<table class="table script-table">
|
|
290
|
+
<tbody>
|
|
291
|
+
<tr><th scope="row">Deck Name</th><td>{{script.deck}}</td></tr>
|
|
292
|
+
<tr><th scope="row">Script Name</th><td>{{ script.name }}</td></tr>
|
|
293
|
+
<tr>
|
|
294
|
+
<th scope="row">Editing status <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="You can choose to disable editing, so the script is finalized and cannot be edited. Use save as to rename the script"><i class="bi bi-info-circle"></i></a></th>
|
|
295
|
+
<td>{{script.status}}</td>
|
|
296
|
+
</tr>
|
|
297
|
+
<tr>
|
|
298
|
+
<th scope="row">Output Values <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This will be your output data. If the return data is not a value, it will save as None is the result file"><i class="bi bi-info-circle"></i></a></th>
|
|
299
|
+
<td>
|
|
300
|
+
{% for i in script.config_return()[1] %}
|
|
301
|
+
<input type="checkbox">{{i}}
|
|
287
302
|
{% endfor %}
|
|
288
|
-
</
|
|
289
|
-
</
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
303
|
+
</td>
|
|
304
|
+
</tr>
|
|
305
|
+
<tr>
|
|
306
|
+
<th scope="row">Config Variables <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This shows variables you want to configure later using .csv file"><i class="bi bi-info-circle"></i></a></th>
|
|
307
|
+
<td>
|
|
308
|
+
<ul>
|
|
309
|
+
{% for i in script.config("script")[0] %}
|
|
310
|
+
<li>{{i}}</li>
|
|
311
|
+
{% endfor %}
|
|
312
|
+
</ul>
|
|
313
|
+
</td>
|
|
314
|
+
</tr>
|
|
315
|
+
</tbody>
|
|
316
|
+
</table>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div class="list-group" id="list" style="margin-top: 20px">
|
|
320
|
+
<ul class="reorder">
|
|
321
|
+
{% for button in buttons %}
|
|
322
|
+
<li id="{{ button['id'] }}" style="list-style-type: none;">
|
|
323
|
+
<span class="line-number d-none">{{ button['id'] }}.</span>
|
|
324
|
+
<a href="{{ url_for('design.edit_action', uuid=button['uuid']) }}" type="button" class="btn btn-light" style="{{ button['style'] }}">{{ button['label'] }}</a>
|
|
325
|
+
{% if not button["instrument"] in ["if","while","repeat"] %}
|
|
326
|
+
<a href="{{ url_for('design.duplicate_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-copy"></span></a>
|
|
327
|
+
{% endif %}
|
|
328
|
+
<a href="{{ url_for('design.delete_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-trash"></span></a>
|
|
329
|
+
</li>
|
|
330
|
+
{% endfor %}
|
|
331
|
+
</ul>
|
|
307
332
|
|
|
333
|
+
<!-- Python Code Overlay -->
|
|
334
|
+
<!-- Right side: Python code (conditionally shown) -->
|
|
335
|
+
<!-- Python Code Slide-Over Panel -->
|
|
336
|
+
{% if session.get('show_code') %}
|
|
337
|
+
<div id="pythonCodeOverlay" class="code-overlay bg-light border-start show">
|
|
338
|
+
<div class="overlay-header d-flex justify-content-between align-items-center px-3 py-2 border-bottom">
|
|
339
|
+
<strong>Python Code</strong>
|
|
340
|
+
<button class="btn btn-sm btn-outline-secondary" onclick="toggleCodeOverlay(false)">
|
|
341
|
+
<i class="bi bi-x-lg"></i>
|
|
342
|
+
</button>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="overlay-content p-3">
|
|
345
|
+
{% for stype, script in session['python_code'].items() %}
|
|
346
|
+
<pre><code class="language-python">{{ script }}</code></pre>
|
|
347
|
+
{% endfor %}
|
|
348
|
+
<a href="{{ url_for('design.download', filetype='python') }}">Download <i class="bi bi-download"></i></a>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
{% endif %}
|
|
352
|
+
</div>
|
|
308
353
|
</div>
|
|
309
|
-
</div>
|
|
310
354
|
<div>
|
|
311
355
|
<a class="btn btn-dark {{ 'disabled' if not script.name or script.status == "finalized" else ''}}" href="{{url_for('database.publish')}}">Quick Save</a>
|
|
312
356
|
<a class="btn btn-dark " href="{{ url_for('design.experiment_run') }}">Compile and Run</a>
|
|
@@ -445,6 +489,17 @@
|
|
|
445
489
|
}
|
|
446
490
|
}
|
|
447
491
|
|
|
492
|
+
function toggleCodeOverlay() {
|
|
493
|
+
const overlay = document.getElementById("pythonCodeOverlay");
|
|
494
|
+
const toggleBtn = document.getElementById("codeToggleBtn");
|
|
495
|
+
overlay.classList.toggle("show");
|
|
496
|
+
|
|
497
|
+
// Change arrow icon
|
|
498
|
+
const icon = toggleBtn.querySelector("i");
|
|
499
|
+
icon.classList.toggle("bi-chevron-left");
|
|
500
|
+
icon.classList.toggle("bi-chevron-right");
|
|
501
|
+
}
|
|
502
|
+
|
|
448
503
|
// Restore state on page load
|
|
449
504
|
document.addEventListener('DOMContentLoaded', () => {
|
|
450
505
|
const savedState = localStorage.getItem('showLineNumbers');
|