ivoryos 1.2.7__py3-none-any.whl → 1.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +9 -244
- ivoryos/app.py +94 -0
- ivoryos/routes/control/control.py +8 -6
- ivoryos/routes/control/templates/controllers.html +18 -0
- ivoryos/routes/control/utils.py +2 -0
- ivoryos/routes/design/design.py +11 -4
- ivoryos/routes/design/templates/components/action_form.html +2 -2
- ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
- ivoryos/server.py +168 -0
- ivoryos/static/js/socket_handler.js +39 -4
- ivoryos/static/js/sortable_design.js +28 -11
- ivoryos/utils/db_models.py +40 -5
- ivoryos/utils/decorators.py +33 -0
- ivoryos/utils/form.py +4 -2
- ivoryos/utils/global_config.py +10 -0
- ivoryos/utils/script_runner.py +21 -2
- ivoryos/utils/task_runner.py +7 -2
- ivoryos/utils/utils.py +20 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.2.7.dist-info → ivoryos-1.2.8.dist-info}/METADATA +1 -1
- {ivoryos-1.2.7.dist-info → ivoryos-1.2.8.dist-info}/RECORD +24 -21
- {ivoryos-1.2.7.dist-info → ivoryos-1.2.8.dist-info}/WHEEL +0 -0
- {ivoryos-1.2.7.dist-info → ivoryos-1.2.8.dist-info}/licenses/LICENSE +0 -0
- {ivoryos-1.2.7.dist-info → ivoryos-1.2.8.dist-info}/top_level.txt +0 -0
ivoryos/__init__.py
CHANGED
|
@@ -1,248 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import sys
|
|
3
|
-
import uuid
|
|
4
|
-
from typing import Union
|
|
5
|
-
|
|
6
|
-
from flask import Flask, redirect, url_for, g, Blueprint, session
|
|
7
|
-
from flask_login import AnonymousUserMixin
|
|
8
|
-
|
|
9
|
-
# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
10
|
-
|
|
11
|
-
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
|
|
19
|
-
from ivoryos.socket_handlers import socketio
|
|
20
|
-
from ivoryos.routes.main.main import main
|
|
21
|
-
# from ivoryos.routes.monitor.monitor import monitor
|
|
22
|
-
from ivoryos.utils import utils
|
|
23
|
-
from ivoryos.utils.db_models import db, User
|
|
24
|
-
from ivoryos.utils.global_config import GlobalConfig
|
|
1
|
+
from ivoryos.server import run
|
|
25
2
|
from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
|
|
26
|
-
from ivoryos.utils.script_runner import ScriptRunner
|
|
27
3
|
from ivoryos.version import __version__ as ivoryos_version
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
global_config = GlobalConfig()
|
|
31
|
-
from sqlalchemy import event
|
|
32
|
-
from sqlalchemy.engine import Engine
|
|
33
|
-
import sqlite3
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@event.listens_for(Engine, "connect")
|
|
37
|
-
def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
|
|
38
|
-
if isinstance(dbapi_connection, sqlite3.Connection):
|
|
39
|
-
cursor = dbapi_connection.cursor()
|
|
40
|
-
cursor.execute("PRAGMA foreign_keys=ON")
|
|
41
|
-
cursor.close()
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
55
|
-
@login_manager.user_loader
|
|
56
|
-
def load_user(user_id):
|
|
57
|
-
"""
|
|
58
|
-
This function is called by Flask-Login on every request to get the
|
|
59
|
-
current user object from the user ID stored in the session.
|
|
60
|
-
"""
|
|
61
|
-
# The correct implementation is to fetch the user from the database.
|
|
62
|
-
return db.session.get(User, user_id)
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
128
|
-
def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, model=None,
|
|
129
|
-
config: Config = None,
|
|
130
|
-
logger: Union[str, list] = None,
|
|
131
|
-
logger_output_name: str = None,
|
|
132
|
-
enable_design: bool = True,
|
|
133
|
-
blueprint_plugins: Union[list, Blueprint] = [],
|
|
134
|
-
exclude_names: list = [],
|
|
135
|
-
):
|
|
136
|
-
"""
|
|
137
|
-
Start ivoryOS app server.
|
|
138
|
-
|
|
139
|
-
:param module: module name, __name__ for current module
|
|
140
|
-
:param host: host address, defaults to 0.0.0.0
|
|
141
|
-
:param port: port, defaults to None, and will use 8000
|
|
142
|
-
:param debug: debug mode, defaults to None (True)
|
|
143
|
-
:param llm_server: llm server, defaults to None.
|
|
144
|
-
:param model: llm model, defaults to None. If None, app will run without text-to-code feature
|
|
145
|
-
:param config: config class, defaults to None
|
|
146
|
-
:param logger: logger name of list of logger names, defaults to None
|
|
147
|
-
:param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
|
|
148
|
-
:param enable_design: enable design canvas, database and workflow execution
|
|
149
|
-
:param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
|
|
150
|
-
:param exclude_names: list[str] module names to exclude from parsing
|
|
151
|
-
"""
|
|
152
|
-
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
153
|
-
|
|
154
|
-
# plugins = load_installed_plugins(app, socketio)
|
|
155
|
-
plugins = []
|
|
156
|
-
if blueprint_plugins:
|
|
157
|
-
config_plugins = load_plugins(blueprint_plugins, app, socketio)
|
|
158
|
-
plugins.extend(config_plugins)
|
|
159
|
-
|
|
160
|
-
def inject_nav_config():
|
|
161
|
-
"""Make NAV_CONFIG available globally to all templates."""
|
|
162
|
-
return dict(
|
|
163
|
-
enable_design=enable_design,
|
|
164
|
-
plugins=plugins,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
app.context_processor(inject_nav_config)
|
|
168
|
-
port = port or int(os.environ.get("PORT", 8000))
|
|
169
|
-
debug = debug if debug is not None else app.config.get('DEBUG', True)
|
|
170
|
-
|
|
171
|
-
app.config["LOGGERS"] = logger
|
|
172
|
-
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
|
|
173
|
-
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
174
|
-
dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
|
|
175
|
-
global_config.optimizers = OPTIMIZER_REGISTRY
|
|
176
|
-
if module:
|
|
177
|
-
app.config["MODULE"] = module
|
|
178
|
-
app.config["OFF_LINE"] = False
|
|
179
|
-
global_config.deck = sys.modules[module]
|
|
180
|
-
global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
|
|
181
|
-
output_path=dummy_deck_path,
|
|
182
|
-
save=True,
|
|
183
|
-
exclude_names=exclude_names
|
|
184
|
-
)
|
|
185
|
-
else:
|
|
186
|
-
app.config["OFF_LINE"] = True
|
|
187
|
-
if model:
|
|
188
|
-
app.config["ENABLE_LLM"] = True
|
|
189
|
-
app.config["LLM_MODEL"] = model
|
|
190
|
-
app.config["LLM_SERVER"] = llm_server
|
|
191
|
-
utils.install_and_import('openai')
|
|
192
|
-
from ivoryos.utils.llm_agent import LlmAgent
|
|
193
|
-
global_config.agent = LlmAgent(host=llm_server, model=model,
|
|
194
|
-
output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
|
|
195
|
-
else:
|
|
196
|
-
app.config["ENABLE_LLM"] = False
|
|
197
|
-
if logger and type(logger) is str:
|
|
198
|
-
utils.start_logger(socketio, log_filename=logger_path, logger_name=logger)
|
|
199
|
-
elif type(logger) is list:
|
|
200
|
-
for log in logger:
|
|
201
|
-
utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
|
|
202
|
-
|
|
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}")
|
|
209
|
-
socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
|
|
210
|
-
# return app
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# def load_installed_plugins(app, socketio):
|
|
214
|
-
# """
|
|
215
|
-
# Dynamically load installed plugins and attach Flask-SocketIO.
|
|
216
|
-
# """
|
|
217
|
-
# plugin_names = []
|
|
218
|
-
# for entry_point in entry_points().get("ivoryos.plugins", []):
|
|
219
|
-
# plugin = entry_point.load()
|
|
220
|
-
#
|
|
221
|
-
# # If the plugin has an `init_socketio()` function, pass socketio
|
|
222
|
-
# if hasattr(plugin, 'init_socketio'):
|
|
223
|
-
# plugin.init_socketio(socketio)
|
|
224
|
-
#
|
|
225
|
-
# plugin_names.append(entry_point.name)
|
|
226
|
-
# app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
|
|
227
|
-
#
|
|
228
|
-
# return plugin_names
|
|
4
|
+
from ivoryos.utils.decorators import block, BUILDING_BLOCKS
|
|
229
5
|
|
|
230
6
|
|
|
231
|
-
|
|
232
|
-
""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"""
|
|
239
|
-
plugin_names = []
|
|
240
|
-
if not isinstance(blueprints, list):
|
|
241
|
-
blueprints = [blueprints]
|
|
242
|
-
for blueprint in blueprints:
|
|
243
|
-
# If the plugin has an `init_socketio()` function, pass socketio
|
|
244
|
-
if hasattr(blueprint, 'init_socketio'):
|
|
245
|
-
blueprint.init_socketio(socketio)
|
|
246
|
-
plugin_names.append(blueprint.name)
|
|
247
|
-
app.register_blueprint(blueprint, url_prefix=f"{url_prefix}/{blueprint.name}")
|
|
248
|
-
return plugin_names
|
|
7
|
+
__all__ = [
|
|
8
|
+
"block",
|
|
9
|
+
"BUILDING_BLOCKS",
|
|
10
|
+
"OPTIMIZER_REGISTRY",
|
|
11
|
+
"run",
|
|
12
|
+
"ivoryos_version",
|
|
13
|
+
]
|
ivoryos/app.py
ADDED
|
@@ -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
|
-
|
|
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=
|
|
107
|
-
|
|
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">></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">
|
ivoryos/routes/control/utils.py
CHANGED
|
@@ -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():
|
ivoryos/routes/design/design.py
CHANGED
|
@@ -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"
|
|
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">
|
ivoryos/server.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from flask import Blueprint
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Engine, event
|
|
9
|
+
|
|
10
|
+
# from ivoryos import BUILDING_BLOCKS
|
|
11
|
+
from ivoryos.app import create_app
|
|
12
|
+
from ivoryos.config import Config, get_config
|
|
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
|
|
16
|
+
from ivoryos.socket_handlers import socketio
|
|
17
|
+
from ivoryos.utils import utils
|
|
18
|
+
from ivoryos.utils.db_models import db, User
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
|
|
22
|
+
|
|
23
|
+
@event.listens_for(Engine, "connect")
|
|
24
|
+
def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
|
|
25
|
+
if isinstance(dbapi_connection, sqlite3.Connection):
|
|
26
|
+
cursor = dbapi_connection.cursor()
|
|
27
|
+
cursor.execute("PRAGMA foreign_keys=ON")
|
|
28
|
+
cursor.close()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@login_manager.user_loader
|
|
33
|
+
def load_user(user_id):
|
|
34
|
+
"""
|
|
35
|
+
This function is called by Flask-Login on every request to get the
|
|
36
|
+
current user object from the user ID stored in the session.
|
|
37
|
+
"""
|
|
38
|
+
# The correct implementation is to fetch the user from the database.
|
|
39
|
+
return db.session.get(User, user_id)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, model=None,
|
|
46
|
+
config: Config = None,
|
|
47
|
+
logger: Union[str, list] = None,
|
|
48
|
+
logger_output_name: str = None,
|
|
49
|
+
enable_design: bool = True,
|
|
50
|
+
blueprint_plugins: Union[list, Blueprint] = [],
|
|
51
|
+
exclude_names: list = [],
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Start ivoryOS app server.
|
|
55
|
+
|
|
56
|
+
:param module: module name, __name__ for current module
|
|
57
|
+
:param host: host address, defaults to 0.0.0.0
|
|
58
|
+
:param port: port, defaults to None, and will use 8000
|
|
59
|
+
:param debug: debug mode, defaults to None (True)
|
|
60
|
+
:param llm_server: llm server, defaults to None.
|
|
61
|
+
:param model: llm model, defaults to None. If None, app will run without text-to-code feature
|
|
62
|
+
:param config: config class, defaults to None
|
|
63
|
+
:param logger: logger name of list of logger names, defaults to None
|
|
64
|
+
:param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
|
|
65
|
+
:param enable_design: enable design canvas, database and workflow execution
|
|
66
|
+
:param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
|
|
67
|
+
:param exclude_names: list[str] module names to exclude from parsing
|
|
68
|
+
"""
|
|
69
|
+
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
70
|
+
|
|
71
|
+
# plugins = load_installed_plugins(app, socketio)
|
|
72
|
+
plugins = []
|
|
73
|
+
if blueprint_plugins:
|
|
74
|
+
config_plugins = load_plugins(blueprint_plugins, app, socketio)
|
|
75
|
+
plugins.extend(config_plugins)
|
|
76
|
+
|
|
77
|
+
def inject_nav_config():
|
|
78
|
+
"""Make NAV_CONFIG available globally to all templates."""
|
|
79
|
+
return dict(
|
|
80
|
+
enable_design=enable_design,
|
|
81
|
+
plugins=plugins,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
app.context_processor(inject_nav_config)
|
|
85
|
+
port = port or int(os.environ.get("PORT", 8000))
|
|
86
|
+
debug = debug if debug is not None else app.config.get('DEBUG', True)
|
|
87
|
+
|
|
88
|
+
app.config["LOGGERS"] = logger
|
|
89
|
+
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
|
|
90
|
+
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
91
|
+
dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
|
|
92
|
+
global_config.optimizers = OPTIMIZER_REGISTRY
|
|
93
|
+
if module:
|
|
94
|
+
app.config["MODULE"] = module
|
|
95
|
+
app.config["OFF_LINE"] = False
|
|
96
|
+
global_config.deck = sys.modules[module]
|
|
97
|
+
global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
|
|
98
|
+
output_path=dummy_deck_path,
|
|
99
|
+
save=True,
|
|
100
|
+
exclude_names=exclude_names
|
|
101
|
+
)
|
|
102
|
+
global_config.building_blocks = utils.create_block_snapshot()
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
app.config["OFF_LINE"] = True
|
|
106
|
+
if model:
|
|
107
|
+
app.config["ENABLE_LLM"] = True
|
|
108
|
+
app.config["LLM_MODEL"] = model
|
|
109
|
+
app.config["LLM_SERVER"] = llm_server
|
|
110
|
+
utils.install_and_import('openai')
|
|
111
|
+
from ivoryos.utils.llm_agent import LlmAgent
|
|
112
|
+
global_config.agent = LlmAgent(host=llm_server, model=model,
|
|
113
|
+
output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
|
|
114
|
+
else:
|
|
115
|
+
app.config["ENABLE_LLM"] = False
|
|
116
|
+
if logger and type(logger) is str:
|
|
117
|
+
utils.start_logger(socketio, log_filename=logger_path, logger_name=logger)
|
|
118
|
+
elif type(logger) is list:
|
|
119
|
+
for log in logger:
|
|
120
|
+
utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
|
|
121
|
+
|
|
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}")
|
|
128
|
+
socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
|
|
129
|
+
# return app
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# def load_installed_plugins(app, socketio):
|
|
133
|
+
# """
|
|
134
|
+
# Dynamically load installed plugins and attach Flask-SocketIO.
|
|
135
|
+
# """
|
|
136
|
+
# plugin_names = []
|
|
137
|
+
# for entry_point in entry_points().get("ivoryos.plugins", []):
|
|
138
|
+
# plugin = entry_point.load()
|
|
139
|
+
#
|
|
140
|
+
# # If the plugin has an `init_socketio()` function, pass socketio
|
|
141
|
+
# if hasattr(plugin, 'init_socketio'):
|
|
142
|
+
# plugin.init_socketio(socketio)
|
|
143
|
+
#
|
|
144
|
+
# plugin_names.append(entry_point.name)
|
|
145
|
+
# app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
|
|
146
|
+
#
|
|
147
|
+
# return plugin_names
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
|
|
151
|
+
"""
|
|
152
|
+
Dynamically load installed plugins and attach Flask-SocketIO.
|
|
153
|
+
:param blueprints: Union[list, Blueprint] list of Blueprint objects or a single Blueprint object
|
|
154
|
+
:param app: Flask application instance
|
|
155
|
+
:param socketio: Flask-SocketIO instance
|
|
156
|
+
:return: list of plugin names
|
|
157
|
+
"""
|
|
158
|
+
plugin_names = []
|
|
159
|
+
if not isinstance(blueprints, list):
|
|
160
|
+
blueprints = [blueprints]
|
|
161
|
+
for blueprint in blueprints:
|
|
162
|
+
# If the plugin has an `init_socketio()` function, pass socketio
|
|
163
|
+
if hasattr(blueprint, 'init_socketio'):
|
|
164
|
+
blueprint.init_socketio(socketio)
|
|
165
|
+
plugin_names.append(blueprint.name)
|
|
166
|
+
app.register_blueprint(blueprint, url_prefix=f"{url_prefix}/{blueprint.name}")
|
|
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');
|
|
42
|
-
|
|
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('
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
ivoryos/utils/db_models.py
CHANGED
|
@@ -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"{
|
|
614
|
+
single_line = f"{function_call}(**{args_str})"
|
|
599
615
|
elif isinstance(args, str):
|
|
600
|
-
single_line = f"{
|
|
616
|
+
single_line = f"{function_call} = {args}"
|
|
601
617
|
else:
|
|
602
|
-
single_line = f"{
|
|
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
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
BUILDING_BLOCKS = {}
|
|
5
|
+
|
|
6
|
+
def block(_func=None, *, category="general"):
|
|
7
|
+
def decorator(func):
|
|
8
|
+
if category not in BUILDING_BLOCKS:
|
|
9
|
+
BUILDING_BLOCKS[category] = {}
|
|
10
|
+
if func.__module__ == "__main__":
|
|
11
|
+
file_path = inspect.getfile(func) # e.g. /path/to/math_blocks.py
|
|
12
|
+
module = os.path.splitext(os.path.basename(file_path))[0]
|
|
13
|
+
else:
|
|
14
|
+
module = func.__module__
|
|
15
|
+
BUILDING_BLOCKS[category][func.__name__] = {
|
|
16
|
+
"func": func,
|
|
17
|
+
"signature": inspect.signature(func),
|
|
18
|
+
"docstring": inspect.getdoc(func),
|
|
19
|
+
"path": module
|
|
20
|
+
}
|
|
21
|
+
return func
|
|
22
|
+
if _func is None:
|
|
23
|
+
return decorator
|
|
24
|
+
else:
|
|
25
|
+
return decorator(_func)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BlockNamespace:
|
|
29
|
+
"""[not in use] Expose methods for one block category as attributes."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, methods):
|
|
32
|
+
for name, meta in methods.items():
|
|
33
|
+
setattr(self, name, meta["func"])
|
ivoryos/utils/form.py
CHANGED
|
@@ -429,7 +429,7 @@ def create_form_from_action(action: dict, script=None, design=True):
|
|
|
429
429
|
|
|
430
430
|
def create_all_builtin_forms(script):
|
|
431
431
|
all_builtin_forms = {}
|
|
432
|
-
for logic_name in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
432
|
+
for logic_name in ['if', 'while', 'variable', 'wait', 'repeat', 'pause']:
|
|
433
433
|
# signature = info.get('signature', {})
|
|
434
434
|
form_class = create_builtin_form(logic_name, script)
|
|
435
435
|
all_builtin_forms[logic_name] = form_class()
|
|
@@ -444,7 +444,8 @@ def create_builtin_form(logic_type, script):
|
|
|
444
444
|
|
|
445
445
|
placeholder_text = {
|
|
446
446
|
'wait': 'Enter second',
|
|
447
|
-
'repeat': 'Enter an integer'
|
|
447
|
+
'repeat': 'Enter an integer',
|
|
448
|
+
'pause': 'Human Intervention Message'
|
|
448
449
|
}.get(logic_type, 'Enter statement')
|
|
449
450
|
description_text = {
|
|
450
451
|
'variable': 'Your variable can be numbers, boolean (True or False) or text ("text")',
|
|
@@ -536,6 +537,7 @@ def _action_button(action: dict, variables: dict):
|
|
|
536
537
|
"repeat": "background-color: lightsteelblue",
|
|
537
538
|
"if": "background-color: salmon",
|
|
538
539
|
"while": "background-color: salmon",
|
|
540
|
+
"pause": "background-color: goldenrod",
|
|
539
541
|
}.get(action['instrument'], "")
|
|
540
542
|
|
|
541
543
|
if action['instrument'] in ['if', 'while', 'repeat']:
|
ivoryos/utils/global_config.py
CHANGED
|
@@ -8,6 +8,7 @@ class GlobalConfig:
|
|
|
8
8
|
if cls._instance is None:
|
|
9
9
|
cls._instance = super(GlobalConfig, cls).__new__(cls, *args, **kwargs)
|
|
10
10
|
cls._instance._deck = None
|
|
11
|
+
cls._instance._building_blocks = None
|
|
11
12
|
cls._instance._registered_workflows = None
|
|
12
13
|
cls._instance._agent = None
|
|
13
14
|
cls._instance._defined_variables = {}
|
|
@@ -27,6 +28,15 @@ class GlobalConfig:
|
|
|
27
28
|
if self._deck is None:
|
|
28
29
|
self._deck = value
|
|
29
30
|
|
|
31
|
+
@property
|
|
32
|
+
def building_blocks(self):
|
|
33
|
+
return self._building_blocks
|
|
34
|
+
|
|
35
|
+
@building_blocks.setter
|
|
36
|
+
def building_blocks(self, value):
|
|
37
|
+
if self._building_blocks is None:
|
|
38
|
+
self._building_blocks = value
|
|
39
|
+
|
|
30
40
|
@property
|
|
31
41
|
def registered_workflows(self):
|
|
32
42
|
return self._registered_workflows
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -8,12 +8,18 @@ from datetime import datetime
|
|
|
8
8
|
from ivoryos.utils import utils, bo_campaign
|
|
9
9
|
from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep, db, SingleStep
|
|
10
10
|
from ivoryos.utils.global_config import GlobalConfig
|
|
11
|
+
from ivoryos.utils.decorators import BUILDING_BLOCKS
|
|
11
12
|
|
|
12
13
|
global_config = GlobalConfig()
|
|
13
14
|
global deck
|
|
14
15
|
deck = None
|
|
15
16
|
# global deck, registered_workflows
|
|
16
17
|
# deck, registered_workflows = None, None
|
|
18
|
+
class HumanInterventionRequired(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def pause(reason="Human intervention required"):
|
|
22
|
+
raise HumanInterventionRequired(reason)
|
|
17
23
|
|
|
18
24
|
class ScriptRunner:
|
|
19
25
|
def __init__(self, globals_dict=None):
|
|
@@ -110,9 +116,15 @@ class ScriptRunner:
|
|
|
110
116
|
# Parse function body from string
|
|
111
117
|
temp_connections = global_config.defined_variables
|
|
112
118
|
# Prepare execution environment
|
|
113
|
-
exec_globals = {"deck": deck, "time":time} # Add required global objects
|
|
119
|
+
exec_globals = {"deck": deck, "time":time, "pause": pause} # Add required global objects
|
|
114
120
|
# exec_globals = {"deck": deck, "time": time, "registered_workflows":registered_workflows} # Add required global objects
|
|
115
121
|
exec_globals.update(temp_connections)
|
|
122
|
+
|
|
123
|
+
# Inject all block categories
|
|
124
|
+
for category, data in BUILDING_BLOCKS.items():
|
|
125
|
+
for method_name, method in data.items():
|
|
126
|
+
exec_globals[method_name] = method["func"]
|
|
127
|
+
|
|
116
128
|
exec_locals = {} # Local execution scope
|
|
117
129
|
|
|
118
130
|
# Define function arguments manually in exec_locals
|
|
@@ -161,6 +173,13 @@ class ScriptRunner:
|
|
|
161
173
|
else:
|
|
162
174
|
exec(line, exec_globals, exec_locals)
|
|
163
175
|
step.run_error = False
|
|
176
|
+
|
|
177
|
+
except HumanInterventionRequired as e:
|
|
178
|
+
logger.warning(f"Human intervention required: {e}")
|
|
179
|
+
socketio.emit('human_intervention', {'message': str(e)})
|
|
180
|
+
# Instead of auto-resume, explicitly stay paused until user action
|
|
181
|
+
self.toggle_pause()
|
|
182
|
+
|
|
164
183
|
except Exception as e:
|
|
165
184
|
logger.error(f"Error during script execution: {e}")
|
|
166
185
|
socketio.emit('error', {'message': str(e)})
|
|
@@ -230,7 +249,7 @@ class ScriptRunner:
|
|
|
230
249
|
with current_app.app_context():
|
|
231
250
|
run = db.session.get(WorkflowRun, run_id)
|
|
232
251
|
run.end_time = datetime.now()
|
|
233
|
-
run.
|
|
252
|
+
run.data_path = filename
|
|
234
253
|
run.run_error = error_flag
|
|
235
254
|
db.session.commit()
|
|
236
255
|
|
ivoryos/utils/task_runner.py
CHANGED
|
@@ -2,6 +2,7 @@ import threading
|
|
|
2
2
|
import time
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
|
+
from ivoryos.utils.decorators import BUILDING_BLOCKS
|
|
5
6
|
from ivoryos.utils.db_models import db, SingleStep
|
|
6
7
|
from ivoryos.utils.global_config import GlobalConfig
|
|
7
8
|
|
|
@@ -48,16 +49,20 @@ class TaskRunner:
|
|
|
48
49
|
if component.startswith("deck."):
|
|
49
50
|
component = component.split(".")[1]
|
|
50
51
|
instrument = getattr(deck, component)
|
|
52
|
+
function_executable = getattr(instrument, method)
|
|
53
|
+
elif component.startswith("blocks."):
|
|
54
|
+
component = component.split(".")[1]
|
|
55
|
+
function_executable = BUILDING_BLOCKS[component][method]["func"]
|
|
51
56
|
else:
|
|
52
57
|
temp_connections = global_config.defined_variables
|
|
53
58
|
instrument = temp_connections.get(component)
|
|
54
|
-
|
|
59
|
+
function_executable = getattr(instrument, method)
|
|
55
60
|
return function_executable
|
|
56
61
|
|
|
57
62
|
def _run_single_step(self, component, method, kwargs, current_app=None):
|
|
58
63
|
try:
|
|
59
64
|
function_executable = self._get_executable(component, deck, method)
|
|
60
|
-
method_name = f"{
|
|
65
|
+
method_name = f"{component}.{method}"
|
|
61
66
|
except Exception as e:
|
|
62
67
|
self.lock.release()
|
|
63
68
|
return {"status": "error", "msg": e.__str__()}
|
ivoryos/utils/utils.py
CHANGED
|
@@ -15,7 +15,7 @@ from flask_login import current_user
|
|
|
15
15
|
from flask_socketio import SocketIO
|
|
16
16
|
|
|
17
17
|
from ivoryos.utils.db_models import Script
|
|
18
|
-
|
|
18
|
+
from ivoryos.utils.decorators import BUILDING_BLOCKS
|
|
19
19
|
|
|
20
20
|
def get_script_file():
|
|
21
21
|
"""Get script from Flask session and returns the script"""
|
|
@@ -151,6 +151,7 @@ def _convert_by_str(args, arg_types):
|
|
|
151
151
|
return args
|
|
152
152
|
except Exception:
|
|
153
153
|
raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
|
|
154
|
+
return args
|
|
154
155
|
|
|
155
156
|
|
|
156
157
|
def _convert_by_class(args, arg_types):
|
|
@@ -364,6 +365,24 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
|
|
|
364
365
|
return deck_snapshot
|
|
365
366
|
|
|
366
367
|
|
|
368
|
+
def create_block_snapshot(save: bool = False, output_path: str = ''):
|
|
369
|
+
block_snapshot = {}
|
|
370
|
+
included = {}
|
|
371
|
+
failed = {}
|
|
372
|
+
for category, data in BUILDING_BLOCKS.items():
|
|
373
|
+
key = f"blocks.{category}"
|
|
374
|
+
block_snapshot[key] = {}
|
|
375
|
+
|
|
376
|
+
for func_name, meta in data.items():
|
|
377
|
+
func = meta["func"]
|
|
378
|
+
block_snapshot[key][func_name] = {
|
|
379
|
+
"signature": meta["signature"],
|
|
380
|
+
"docstring": meta["docstring"],
|
|
381
|
+
"path": f"{func.__module__}.{func.__qualname__}"
|
|
382
|
+
}
|
|
383
|
+
print(block_snapshot)
|
|
384
|
+
return block_snapshot
|
|
385
|
+
|
|
367
386
|
def load_deck(pkl_name: str):
|
|
368
387
|
"""
|
|
369
388
|
Loads a pickled deck snapshot from disk on offline mode
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.8"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
ivoryos/__init__.py,sha256=
|
|
1
|
+
ivoryos/__init__.py,sha256=eUtNgSskl--l94VUTT1bgiBR8gdMMFQgjHEsHOxdHyI,320
|
|
2
|
+
ivoryos/app.py,sha256=yU3f4mu2LL82NEPvQid5GWdfrIa4F_04Feb0bl-ICV4,3323
|
|
2
3
|
ivoryos/config.py,sha256=y3RxNjiIola9tK7jg-mHM8EzLMwiLwOzoisXkDvj0gA,2174
|
|
4
|
+
ivoryos/server.py,sha256=2ka-xqWpuY9HIqxtA24jU_dIX51DRx1ccejpJnBKgaE,6742
|
|
3
5
|
ivoryos/socket_handlers.py,sha256=VWVWiIdm4jYAutwGu6R0t1nK5MuMyOCL0xAnFn06jWQ,1302
|
|
4
|
-
ivoryos/version.py,sha256=
|
|
6
|
+
ivoryos/version.py,sha256=CfVXm0wwlKPW0khOcwhWw61TpgtZiLijCePsAIOK3aU,22
|
|
5
7
|
ivoryos/optimizer/ax_optimizer.py,sha256=PoSu8hrDFFpqyhRBnaSMswIUsDfEX6sPWt8NEZ_sobs,7112
|
|
6
8
|
ivoryos/optimizer/base_optimizer.py,sha256=JTbUharZKn0t8_BDbAFuwZIbT1VOnX1Xuog1pJuU8hY,1992
|
|
7
9
|
ivoryos/optimizer/baybe_optimizer.py,sha256=EdrrRiYO-IOx610cPXiQhH4qG8knUP0uiZ0YoyaGIU8,7954
|
|
@@ -13,11 +15,11 @@ ivoryos/routes/auth/auth.py,sha256=CqoP9cM8BuXVGHGujX7-0sNAOdWILU9amyBrObOD6Ss,3
|
|
|
13
15
|
ivoryos/routes/auth/templates/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
|
|
14
16
|
ivoryos/routes/auth/templates/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
|
|
15
17
|
ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
ivoryos/routes/control/control.py,sha256=
|
|
18
|
+
ivoryos/routes/control/control.py,sha256=6LnVF4mGgfLQvzmrSFxaFz9lBtBe4WnXlIouDxtaR2E,6230
|
|
17
19
|
ivoryos/routes/control/control_file.py,sha256=3fQ9R8EcdqKs_hABn2EqRAB1xC2DHAT_q_pwsMIDDQI,864
|
|
18
20
|
ivoryos/routes/control/control_new_device.py,sha256=mfJKg5JAOagIpUKbp2b5nRwvd2V3bzT3M0zIhIsEaFM,5456
|
|
19
|
-
ivoryos/routes/control/utils.py,sha256=
|
|
20
|
-
ivoryos/routes/control/templates/controllers.html,sha256=
|
|
21
|
+
ivoryos/routes/control/utils.py,sha256=XlhhqAtOj7n3XfHPDxJ8TvCV2K2I2IixB0CBkl1QeQc,1242
|
|
22
|
+
ivoryos/routes/control/templates/controllers.html,sha256=XoV1HVjB6ho4Ah8HerqqmbiWK0N_HyFKhmqu4E2JXdM,8966
|
|
21
23
|
ivoryos/routes/control/templates/controllers_new.html,sha256=eVeLABT39DWOIYrwWClw7sAD3lCoAGCznygPgFbQoRc,5945
|
|
22
24
|
ivoryos/routes/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
25
|
ivoryos/routes/data/data.py,sha256=AoqCaIAK0f9hstF1pxlJFeK_J-wKbMfXWGNDUbaBFFk,4218
|
|
@@ -25,11 +27,11 @@ ivoryos/routes/data/templates/workflow_database.html,sha256=ofvHcovpwmJXo1SFiSrL
|
|
|
25
27
|
ivoryos/routes/data/templates/workflow_view.html,sha256=72xKreX9WhYx-0n0cFf-CL-fJIWXPCIaTi_Aa8Tq3xg,3651
|
|
26
28
|
ivoryos/routes/data/templates/components/step_card.html,sha256=9lKR4NCgU2v5Nbdv2uaJ-9aKibtiB_2-Y_kyHX6Ka1k,730
|
|
27
29
|
ivoryos/routes/design/__init__.py,sha256=zS3HXKaw0ALL5n6t_W1rUz5Uj5_tTQ-Y1VMXyzewvR0,113
|
|
28
|
-
ivoryos/routes/design/design.py,sha256=
|
|
30
|
+
ivoryos/routes/design/design.py,sha256=xYDwtCdTcCd282guaIeNvfUFc5UsiypkQVpRvFqRujQ,18246
|
|
29
31
|
ivoryos/routes/design/design_file.py,sha256=m4yku8fkpLUs4XvLJBqR5V-kyaGKbGB6ZoRxGbjEU5Q,2140
|
|
30
32
|
ivoryos/routes/design/design_step.py,sha256=l8U3-FuXmap__sYm51AueKdbTaLCFaKjAz-j02b4g-E,5200
|
|
31
33
|
ivoryos/routes/design/templates/experiment_builder.html,sha256=hh-d2tOc_40gww5WfUYIf8sM3qBaALZnR8Sx7Ja4tpU,1623
|
|
32
|
-
ivoryos/routes/design/templates/components/action_form.html,sha256=
|
|
34
|
+
ivoryos/routes/design/templates/components/action_form.html,sha256=kXJOrJLbFsMHHWVSuMQHpt1xFrUMnwgzTG8e6Qfn0Cg,3042
|
|
33
35
|
ivoryos/routes/design/templates/components/actions_panel.html,sha256=jHTR58saTUIZInBdC-vLc1ZTbStLiULeWbupjB4hQzo,977
|
|
34
36
|
ivoryos/routes/design/templates/components/autofill_toggle.html,sha256=CRVQUHoQT7sOSO5-Vax54ImHdT4G_mEgqR5OQkeUwK8,617
|
|
35
37
|
ivoryos/routes/design/templates/components/canvas.html,sha256=bKLCJaG1B36Yy9Vsnz4P5qiX4BPdfaGe9JeQQzu9rsI,268
|
|
@@ -38,7 +40,7 @@ ivoryos/routes/design/templates/components/canvas_header.html,sha256=7iIzLDGHX7M
|
|
|
38
40
|
ivoryos/routes/design/templates/components/canvas_main.html,sha256=9inYO700zRa09lfQI2NY4FJGGeTh-9rvX4ltjj0LK3k,1432
|
|
39
41
|
ivoryos/routes/design/templates/components/deck_selector.html,sha256=ryTRpljYezo0AzGLCJu_qOMokjjnft3GIxddmNGtBA0,657
|
|
40
42
|
ivoryos/routes/design/templates/components/edit_action_form.html,sha256=Dz7FnnOK4PYptAHNy9_WFCU1RZTSV61-1lNHHOSRJNs,1876
|
|
41
|
-
ivoryos/routes/design/templates/components/instruments_panel.html,sha256=
|
|
43
|
+
ivoryos/routes/design/templates/components/instruments_panel.html,sha256=tRKd-wOqKjaMJCLuGgRmHtxIgSjklhBkuX8arm5aTCU,4268
|
|
42
44
|
ivoryos/routes/design/templates/components/modals.html,sha256=6Dl8I8oD4ln7kK8C5e92pFVVH5KDte-vVTL0U_6NSTg,306
|
|
43
45
|
ivoryos/routes/design/templates/components/python_code_overlay.html,sha256=GUHgsmUWQf0P1Fbg5W0OJC34TXCUIMQVUkS7KDoauyI,1264
|
|
44
46
|
ivoryos/routes/design/templates/components/sidebar.html,sha256=A6dRo53zIB6QJVrRLJcBZHUNJ3qpYPnR3kWxM8gTkjw,501
|
|
@@ -76,25 +78,26 @@ ivoryos/static/js/action_handlers.js,sha256=UJHKFhYRNQRBo0AHLCIxhWxt8OSgYeyLynzP
|
|
|
76
78
|
ivoryos/static/js/db_delete.js,sha256=l67fqUaN_FVDaL7v91Hd7LyRbxnqXx9nyjF34-7aewY,561
|
|
77
79
|
ivoryos/static/js/overlay.js,sha256=dPxop19es0E0ZUSY3d_4exIk7CJuQEnlW5uTt5fZfzI,483
|
|
78
80
|
ivoryos/static/js/script_metadata.js,sha256=m8VYZ8OGT2oTx1kXMXq60bKQI9WCbJNkzcFDzLvRuGc,1188
|
|
79
|
-
ivoryos/static/js/socket_handler.js,sha256=
|
|
81
|
+
ivoryos/static/js/socket_handler.js,sha256=vrpVyYMsFpHIJjqke5LwVttRI6IJMXSx_D0AMhWRg3k,6906
|
|
80
82
|
ivoryos/static/js/sortable_card.js,sha256=ifmlGe3yy0U_KzMphV4ClRhK2DLOvkELYMlq1vECuac,807
|
|
81
|
-
ivoryos/static/js/sortable_design.js,sha256=
|
|
83
|
+
ivoryos/static/js/sortable_design.js,sha256=ASc9P6_423Mczeg6QH6LVtyxLyWhpxWJP2nEEjR9K1M,5474
|
|
82
84
|
ivoryos/static/js/ui_state.js,sha256=XYsOcfGlduqLlqHySvPrRrR50CiAsml51duqneigsRY,3368
|
|
83
85
|
ivoryos/templates/base.html,sha256=cl5w6E8yskbUzdiJFal6fZjnPuFNKEzc7BrrbRd6bMI,8581
|
|
84
86
|
ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
87
|
ivoryos/utils/bo_campaign.py,sha256=Fil-zT7JexL_p9XqyWByjAk42XB1R9XUKN8CdV5bi6c,9714
|
|
86
88
|
ivoryos/utils/client_proxy.py,sha256=74G3HAuq50iEHkSvlMZFmQaukm613FbRgOdzO_T3dMg,10191
|
|
87
|
-
ivoryos/utils/db_models.py,sha256=
|
|
88
|
-
ivoryos/utils/
|
|
89
|
-
ivoryos/utils/
|
|
89
|
+
ivoryos/utils/db_models.py,sha256=aJ9JOmKERXqGrhSTqIbdTGeq16aalDUaOmU3kvZvx0U,29542
|
|
90
|
+
ivoryos/utils/decorators.py,sha256=p1Bdl3dCeaHNv6-cCCUOZMiFu9kRaqqQnkFJUkzPoJE,991
|
|
91
|
+
ivoryos/utils/form.py,sha256=G9wkxCF3aozWBvm865DcCkA6G4dQfNQWKlPejYp5t-U,22311
|
|
92
|
+
ivoryos/utils/global_config.py,sha256=D6oz5dttyaP24jbqnw1sR64moSb-7jJkSpRuufdA_TI,2747
|
|
90
93
|
ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
|
|
91
94
|
ivoryos/utils/py_to_json.py,sha256=fyqjaxDHPh-sahgT6IHSn34ktwf6y51_x1qvhbNlH-U,7314
|
|
92
|
-
ivoryos/utils/script_runner.py,sha256=
|
|
95
|
+
ivoryos/utils/script_runner.py,sha256=gQp37yzlo04h6bxbHloOOim4cLn43S0web7-XN2TQjA,17799
|
|
93
96
|
ivoryos/utils/serilize.py,sha256=lkBhkz8r2bLmz2_xOb0c4ptSSOqjIu6krj5YYK4Nvj8,6784
|
|
94
|
-
ivoryos/utils/task_runner.py,sha256=
|
|
95
|
-
ivoryos/utils/utils.py,sha256
|
|
96
|
-
ivoryos-1.2.
|
|
97
|
-
ivoryos-1.2.
|
|
98
|
-
ivoryos-1.2.
|
|
99
|
-
ivoryos-1.2.
|
|
100
|
-
ivoryos-1.2.
|
|
97
|
+
ivoryos/utils/task_runner.py,sha256=bfG6GubdlzgD8rBwzD00aGB5LDFmb9hLFJIOMH8hVv4,3248
|
|
98
|
+
ivoryos/utils/utils.py,sha256=JfDANbhjD6eBJTKeTtEn6B060jd9HACMLxrrQjJzaAI,14589
|
|
99
|
+
ivoryos-1.2.8.dist-info/licenses/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
|
|
100
|
+
ivoryos-1.2.8.dist-info/METADATA,sha256=lw0l8cYCIOBgPxYj3PxoOcAwyLDUL4T6Ij_DrRQsBXc,7351
|
|
101
|
+
ivoryos-1.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
102
|
+
ivoryos-1.2.8.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
|
|
103
|
+
ivoryos-1.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|