ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html}
RENAMED
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
{% block title %}IvoryOS | Design Database{% endblock %}
|
|
4
4
|
{% block body %}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
<!-- Deck Filter Buttons -->
|
|
6
|
+
<div class="btn-group" role="group">
|
|
7
|
+
{% for deck in deck_list %}
|
|
8
|
+
<a class="btn {% if deck == current_deck_name %}btn-primary{% else %}btn-secondary{% endif %}"
|
|
9
|
+
href="{{ url_for('library.load_from_database', deck_name=deck) }}">
|
|
10
|
+
{{ deck }}
|
|
11
|
+
</a>
|
|
10
12
|
{% endfor %}
|
|
11
|
-
|
|
12
|
-
<form id="search" style="display: inline-block;float: right;" action="{{url_for('database.load_from_database',deck_name=deck_name)}}" method="GET">
|
|
13
|
-
<div class="input-group">
|
|
14
|
-
<div class="form-outline">
|
|
15
|
-
<input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
|
|
16
|
-
</div>
|
|
17
|
-
<button type="submit" class="btn btn-primary">
|
|
18
|
-
<i class="bi bi-search"></i>
|
|
19
|
-
</button>
|
|
20
|
-
</div>
|
|
21
|
-
</form>
|
|
22
13
|
</div>
|
|
23
14
|
|
|
15
|
+
<!-- Search Form -->
|
|
16
|
+
<form id="search" class="d-flex " method="GET" style="display: inline-block;float: right;"
|
|
17
|
+
action="{{ url_for('library.load_from_database', deck_name=current_deck_name or 'ALL') }}">
|
|
18
|
+
<div class="input-group">
|
|
19
|
+
<input type="search" name="keyword" id="keyword" class="form-control"
|
|
20
|
+
placeholder="Search workflows..." value="{{ request.args.get('keyword', '') }}">
|
|
21
|
+
<button type="submit" class="btn btn-primary">
|
|
22
|
+
<i class="bi bi-search"></i>
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</form>
|
|
26
|
+
|
|
24
27
|
<table class="table table-hover" id="workflowLibrary">
|
|
25
28
|
<thead>
|
|
26
29
|
<tr>
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
<tbody>
|
|
38
41
|
{% for script in scripts %}
|
|
39
42
|
<tr>
|
|
40
|
-
<td><a href="{{ url_for('
|
|
43
|
+
<td><a href="{{ url_for('library.workflow_script', script_name=script.name) }}">{{ script.name }}</a></td>
|
|
41
44
|
<td>{{ script.deck }}</td>
|
|
42
45
|
<td>{{ script.status }}</td>
|
|
43
46
|
<td>{{ script.time_created }}</td>
|
|
@@ -46,8 +49,14 @@
|
|
|
46
49
|
{# <td>{{ workflow.registered }}</td>#}
|
|
47
50
|
<td>
|
|
48
51
|
{#not workflow.status == "finalized" or#}
|
|
49
|
-
{%
|
|
50
|
-
|
|
52
|
+
{% set username = current_user.get_id() %}
|
|
53
|
+
{% if username == 'admin' or username == script.author %}
|
|
54
|
+
<a href="#"
|
|
55
|
+
class="text-danger"
|
|
56
|
+
data-delete-url="{{ url_for('library.workflow_script', script_name=script.name) }}"
|
|
57
|
+
onclick="deleteWorkflow(this); return false;">
|
|
58
|
+
Delete
|
|
59
|
+
</a>
|
|
51
60
|
{% else %}
|
|
52
61
|
<a class="disabled-link">delete</a>
|
|
53
62
|
{% endif %}
|
|
@@ -60,13 +69,13 @@
|
|
|
60
69
|
{# paging#}
|
|
61
70
|
<div class="pagination justify-content-center">
|
|
62
71
|
<div class="page-item {{ 'disabled' if not scripts.has_prev else '' }}">
|
|
63
|
-
<a class="page-link" href="{{ url_for('
|
|
72
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.prev_num) }}">Previous</a>
|
|
64
73
|
</div>
|
|
65
74
|
|
|
66
75
|
{% for num in scripts.iter_pages() %}
|
|
67
76
|
{% if num %}
|
|
68
77
|
<div class="page-item {{ 'active' if num == scripts.page else '' }}">
|
|
69
|
-
<a class="page-link" href="{{ url_for('
|
|
78
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=num) }}">{{ num }}</a>
|
|
70
79
|
</div>
|
|
71
80
|
{% else %}
|
|
72
81
|
<div class="page-item disabled">
|
|
@@ -76,8 +85,8 @@
|
|
|
76
85
|
{% endfor %}
|
|
77
86
|
|
|
78
87
|
<div class="page-item {{ 'disabled' if not scripts.has_next else '' }}">
|
|
79
|
-
<a class="page-link" href="{{ url_for('
|
|
88
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.next_num) }}">Next</a>
|
|
80
89
|
</div>
|
|
81
90
|
</div>
|
|
82
|
-
|
|
91
|
+
<script src="{{ url_for('static', filename='js/db_delete.js') }}"></script>
|
|
83
92
|
{% endblock %}
|
ivoryos/routes/main/main.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from flask import Blueprint, render_template, current_app, request, url_for
|
|
4
|
+
from flask_login import login_required, current_user
|
|
5
|
+
from werkzeug.utils import secure_filename, redirect
|
|
6
|
+
from ivoryos.utils.db_models import db
|
|
3
7
|
from ivoryos.version import __version__ as ivoryos_version
|
|
4
8
|
|
|
5
|
-
main = Blueprint('main', __name__, template_folder='templates
|
|
9
|
+
main = Blueprint('main', __name__, template_folder='templates')
|
|
6
10
|
|
|
7
11
|
@main.route("/")
|
|
8
12
|
@login_required
|
|
@@ -40,3 +44,27 @@ def help_info():
|
|
|
40
44
|
ivoryos(__name__)
|
|
41
45
|
"""
|
|
42
46
|
return render_template('help.html', sample_deck=sample_deck)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@main.route('/customize-logo', methods=['POST'])
|
|
50
|
+
@login_required
|
|
51
|
+
def customize_logo():
|
|
52
|
+
if request.method == 'POST':
|
|
53
|
+
file = request.files.get('logo')
|
|
54
|
+
mode = request.form.get('mode')
|
|
55
|
+
|
|
56
|
+
if file and file.filename != '':
|
|
57
|
+
filename = secure_filename(file.filename)
|
|
58
|
+
|
|
59
|
+
USER_LOGO_DIR = os.path.join(current_app.static_folder, "user_logos")
|
|
60
|
+
os.makedirs(USER_LOGO_DIR, exist_ok=True)
|
|
61
|
+
filepath = os.path.join(USER_LOGO_DIR, filename)
|
|
62
|
+
file.save(filepath)
|
|
63
|
+
|
|
64
|
+
# Save to database
|
|
65
|
+
current_user.settings = {"logo_filename": filename, "logo_mode": mode}
|
|
66
|
+
# current_user.logo_mode = mode
|
|
67
|
+
db.session.commit()
|
|
68
|
+
|
|
69
|
+
return redirect(url_for('main.index'))
|
|
70
|
+
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<i class="bi bi-folder2-open me-2"></i>Browse designs
|
|
20
20
|
</h5>
|
|
21
21
|
<p class="card-text">View all saved workflows from the database.</p>
|
|
22
|
-
<a href="{{ url_for('
|
|
22
|
+
<a href="{{ url_for('library.load_from_database') }}" class="stretched-link"></a>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
<i class="bi bi-graph-up-arrow me-2"></i>Experiment data
|
|
49
49
|
</h5>
|
|
50
50
|
<p class="card-text">Browse workflow logs and output data.</p>
|
|
51
|
-
<a href="{{ url_for('
|
|
51
|
+
<a href="{{ url_for('data.list_workflows') }}" class="stretched-link"></a>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
</div>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<i class="bi bi-play-circle me-2"></i>Run current workflow
|
|
63
63
|
</h5>
|
|
64
64
|
<p class="card-text">Execute workflows with configurable parameters.</p>
|
|
65
|
-
<a href="{{ url_for('
|
|
65
|
+
<a href="{{ url_for('execute.experiment_run') }}" class="stretched-link"></a>
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
</div>
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
<i class="bi bi-usb-plug me-2"></i>Connect a new device
|
|
94
94
|
</h5>
|
|
95
95
|
<p class="card-text">Add new hardware temporarily or for testing purposes.</p>
|
|
96
|
-
<a href="{{ url_for('control.
|
|
96
|
+
<a href="{{ url_for('control.temp.new_controller') }}" class="stretched-link"></a>
|
|
97
97
|
</div>
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
ivoryos/server.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
notification_handler=None,
|
|
53
|
+
optimizer_registry: dict = None,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Start ivoryOS app server.
|
|
57
|
+
|
|
58
|
+
:param module: module name, __name__ for current module
|
|
59
|
+
:param host: host address, defaults to 0.0.0.0
|
|
60
|
+
:param port: port, defaults to None, and will use 8000
|
|
61
|
+
:param debug: debug mode, defaults to None (True)
|
|
62
|
+
:param llm_server: llm server, defaults to None.
|
|
63
|
+
:param model: llm model, defaults to None. If None, app will run without text-to-code feature
|
|
64
|
+
:param config: config class, defaults to None
|
|
65
|
+
:param logger: logger name of list of logger names, defaults to None
|
|
66
|
+
:param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
|
|
67
|
+
:param enable_design: enable design canvas, database and workflow execution
|
|
68
|
+
:param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
|
|
69
|
+
:param exclude_names: list[str] module names to exclude from parsing
|
|
70
|
+
:param notification_handler: notification handler function
|
|
71
|
+
"""
|
|
72
|
+
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
73
|
+
|
|
74
|
+
# plugins = load_installed_plugins(app, socketio)
|
|
75
|
+
plugins = []
|
|
76
|
+
if blueprint_plugins:
|
|
77
|
+
config_plugins = load_plugins(blueprint_plugins, app, socketio)
|
|
78
|
+
plugins.extend(config_plugins)
|
|
79
|
+
|
|
80
|
+
def inject_nav_config():
|
|
81
|
+
"""Make NAV_CONFIG available globally to all templates."""
|
|
82
|
+
return dict(
|
|
83
|
+
enable_design=enable_design,
|
|
84
|
+
plugins=plugins,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
app.context_processor(inject_nav_config)
|
|
88
|
+
port = port or int(os.environ.get("PORT", 8000))
|
|
89
|
+
debug = debug if debug is not None else app.config.get('DEBUG', True)
|
|
90
|
+
|
|
91
|
+
app.config["LOGGERS"] = logger
|
|
92
|
+
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
|
|
93
|
+
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
94
|
+
dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
|
|
95
|
+
if optimizer_registry:
|
|
96
|
+
global_config.optimizers = optimizer_registry
|
|
97
|
+
else:
|
|
98
|
+
global_config.optimizers = OPTIMIZER_REGISTRY
|
|
99
|
+
if module:
|
|
100
|
+
app.config["MODULE"] = module
|
|
101
|
+
app.config["OFF_LINE"] = False
|
|
102
|
+
global_config.deck = sys.modules[module]
|
|
103
|
+
global_config.building_blocks = utils.create_block_snapshot()
|
|
104
|
+
global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
|
|
105
|
+
output_path=dummy_deck_path,
|
|
106
|
+
save=True,
|
|
107
|
+
exclude_names=exclude_names
|
|
108
|
+
)
|
|
109
|
+
global_config.api_variables = utils.create_module_snapshot(global_config.deck)
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
app.config["OFF_LINE"] = True
|
|
113
|
+
if model:
|
|
114
|
+
app.config["ENABLE_LLM"] = True
|
|
115
|
+
app.config["LLM_MODEL"] = model
|
|
116
|
+
app.config["LLM_SERVER"] = llm_server
|
|
117
|
+
utils.install_and_import('openai')
|
|
118
|
+
from ivoryos.utils.llm_agent import LlmAgent
|
|
119
|
+
global_config.agent = LlmAgent(host=llm_server, model=model,
|
|
120
|
+
output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
|
|
121
|
+
else:
|
|
122
|
+
app.config["ENABLE_LLM"] = False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --- Logger registration ---
|
|
126
|
+
if logger:
|
|
127
|
+
if isinstance(logger, str):
|
|
128
|
+
logger = [logger] # convert single logger to list
|
|
129
|
+
elif not isinstance(logger, list):
|
|
130
|
+
raise TypeError("logger must be a string or a list of strings.")
|
|
131
|
+
|
|
132
|
+
for log_name in logger:
|
|
133
|
+
utils.start_logger(socketio, log_filename=logger_path, logger_name=log_name)
|
|
134
|
+
|
|
135
|
+
# --- Notification handler registration ---
|
|
136
|
+
if notification_handler:
|
|
137
|
+
|
|
138
|
+
# make it a list if a single function is passed
|
|
139
|
+
if callable(notification_handler):
|
|
140
|
+
notification_handler = [notification_handler]
|
|
141
|
+
|
|
142
|
+
if not isinstance(notification_handler, list):
|
|
143
|
+
raise ValueError("notification_handlers must be a callable or a list of callables.")
|
|
144
|
+
|
|
145
|
+
# validate all items are callable
|
|
146
|
+
for handler in notification_handler:
|
|
147
|
+
if not callable(handler):
|
|
148
|
+
raise TypeError(f"Handler {handler} is not callable.")
|
|
149
|
+
global_config.register_notification(handler)
|
|
150
|
+
|
|
151
|
+
# TODO in case Python 3.12 or higher doesn't log URL
|
|
152
|
+
# if sys.version_info >= (3, 12):
|
|
153
|
+
# ip = utils.get_local_ip()
|
|
154
|
+
# print(f"Server running at http://localhost:{port}")
|
|
155
|
+
# if not ip == "127.0.0.1":
|
|
156
|
+
# print(f"Server running at http://{ip}:{port}")
|
|
157
|
+
socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
|
|
158
|
+
# return app
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
|
|
163
|
+
"""
|
|
164
|
+
Dynamically load installed plugins and attach Flask-SocketIO.
|
|
165
|
+
:param blueprints: Union[list, Blueprint] list of Blueprint objects or a single Blueprint object
|
|
166
|
+
:param app: Flask application instance
|
|
167
|
+
:param socketio: Flask-SocketIO instance
|
|
168
|
+
:return: list of plugin names
|
|
169
|
+
"""
|
|
170
|
+
plugin_names = []
|
|
171
|
+
if not isinstance(blueprints, list):
|
|
172
|
+
blueprints = [blueprints]
|
|
173
|
+
for blueprint in blueprints:
|
|
174
|
+
# If the plugin has an `init_socketio()` function, pass socketio
|
|
175
|
+
if hasattr(blueprint, 'init_socketio'):
|
|
176
|
+
blueprint.init_socketio(socketio)
|
|
177
|
+
plugin_names.append(blueprint.name)
|
|
178
|
+
app.register_blueprint(blueprint, url_prefix=f"{url_prefix}/{blueprint.name}")
|
|
179
|
+
return plugin_names
|
|
180
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from flask import current_app
|
|
3
|
+
from flask_socketio import SocketIO
|
|
4
|
+
from ivoryos.utils.script_runner import ScriptRunner
|
|
5
|
+
|
|
6
|
+
socketio = SocketIO(cors_allowed_origins="*")
|
|
7
|
+
runner = ScriptRunner()
|
|
8
|
+
|
|
9
|
+
def abort_pending():
|
|
10
|
+
runner.abort_pending()
|
|
11
|
+
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
12
|
+
|
|
13
|
+
def abort_current():
|
|
14
|
+
runner.stop_execution()
|
|
15
|
+
socketio.emit('log', {'message': "stopped next task"})
|
|
16
|
+
|
|
17
|
+
def pause():
|
|
18
|
+
runner.retry = False
|
|
19
|
+
msg = runner.toggle_pause()
|
|
20
|
+
socketio.emit('log', {'message': msg})
|
|
21
|
+
return msg
|
|
22
|
+
|
|
23
|
+
def retry():
|
|
24
|
+
runner.retry = True
|
|
25
|
+
msg = runner.toggle_pause()
|
|
26
|
+
socketio.emit('log', {'message': msg})
|
|
27
|
+
|
|
28
|
+
# Socket.IO Event Handlers
|
|
29
|
+
@socketio.on('abort_pending')
|
|
30
|
+
def handle_abort_pending():
|
|
31
|
+
abort_pending()
|
|
32
|
+
|
|
33
|
+
@socketio.on('abort_current')
|
|
34
|
+
def handle_abort_current():
|
|
35
|
+
abort_current()
|
|
36
|
+
|
|
37
|
+
@socketio.on('pause')
|
|
38
|
+
def handle_pause():
|
|
39
|
+
pause()
|
|
40
|
+
|
|
41
|
+
@socketio.on('retry')
|
|
42
|
+
def handle_retry():
|
|
43
|
+
retry()
|
|
44
|
+
|
|
45
|
+
@socketio.on('connect')
|
|
46
|
+
def handle_connect():
|
|
47
|
+
# Fetch log messages from local file
|
|
48
|
+
filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
|
|
49
|
+
with open(filename, 'r') as log_file:
|
|
50
|
+
log_history = log_file.readlines()
|
|
51
|
+
for message in log_history[-10:]:
|
|
52
|
+
socketio.emit('log', {'message': message})
|
|
Binary file
|