ivoryos 0.1.5__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 +94 -0
- ivoryos/config.py +46 -0
- ivoryos/routes/__init__.py +0 -0
- ivoryos/routes/auth/__init__.py +0 -0
- ivoryos/routes/auth/auth.py +65 -0
- ivoryos/routes/auth/templates/auth/login.html +25 -0
- ivoryos/routes/auth/templates/auth/signup.html +32 -0
- ivoryos/routes/control/__init__.py +0 -0
- ivoryos/routes/control/control.py +233 -0
- ivoryos/routes/control/templates/control/controllers.html +71 -0
- ivoryos/routes/control/templates/control/controllers_home.html +50 -0
- ivoryos/routes/control/templates/control/controllers_new.html +89 -0
- ivoryos/routes/database/__init__.py +0 -0
- ivoryos/routes/database/database.py +122 -0
- ivoryos/routes/database/templates/database/experiment_database.html +72 -0
- ivoryos/routes/design/__init__.py +0 -0
- ivoryos/routes/design/design.py +396 -0
- ivoryos/routes/design/templates/design/experiment_builder.html +413 -0
- ivoryos/routes/design/templates/design/experiment_run.html +325 -0
- ivoryos/routes/main/__init__.py +0 -0
- ivoryos/routes/main/main.py +25 -0
- ivoryos/routes/main/templates/main/help.html +144 -0
- ivoryos/routes/main/templates/main/home.html +68 -0
- ivoryos/static/favicon.ico +0 -0
- ivoryos/static/gui_annotation/Slide1.png +0 -0
- ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- ivoryos/static/js/overlay.js +12 -0
- ivoryos/static/js/socket_handler.js +25 -0
- ivoryos/static/js/sortable_card.js +24 -0
- ivoryos/static/js/sortable_design.js +36 -0
- ivoryos/static/logo.png +0 -0
- ivoryos/static/style.css +202 -0
- ivoryos/templates/base.html +141 -0
- ivoryos/utils/__init__.py +0 -0
- ivoryos/utils/db_models.py +501 -0
- ivoryos/utils/form.py +316 -0
- ivoryos/utils/global_config.py +68 -0
- ivoryos/utils/llm_agent.py +183 -0
- ivoryos/utils/script_runner.py +158 -0
- ivoryos/utils/task_manager.py +80 -0
- ivoryos/utils/utils.py +337 -0
- ivoryos-0.1.5.dist-info/LICENSE +21 -0
- ivoryos-0.1.5.dist-info/METADATA +96 -0
- ivoryos-0.1.5.dist-info/RECORD +46 -0
- ivoryos-0.1.5.dist-info/WHEEL +5 -0
- ivoryos-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | New devices{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col-xl-4 col-lg-4 col-md-6 mb-4 ">
|
|
7
|
+
<h5>Available Python API</h5>
|
|
8
|
+
<hr>
|
|
9
|
+
{% for instrument in api_variables %}
|
|
10
|
+
<div class="bg-white rounded shadow-sm position-relative">
|
|
11
|
+
<h5 class="p-3 controller-card">
|
|
12
|
+
<a href="{{ url_for('control.new_controller', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a>
|
|
13
|
+
</h5>
|
|
14
|
+
</div>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
<div class="bg-white rounded shadow-sm position-relative">
|
|
17
|
+
<h5 class="p-3 controller-card">
|
|
18
|
+
<a data-bs-toggle="modal" href="#importAPI" class="stretched-link"><i class="bi bi-folder-plus"></i> Import API</a>
|
|
19
|
+
</h5>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="col-xl-5 col-lg-5 col-md-6 mb-4 ">
|
|
23
|
+
{% if device %}
|
|
24
|
+
<h5>Connecting</h5><hr>
|
|
25
|
+
<form role="form" method='POST' name="init" action="{{ url_for('control.new_controller', instrument=instrument) }}">
|
|
26
|
+
<div class="form-group">
|
|
27
|
+
<div class="input-group mb-3">
|
|
28
|
+
<span class="input-group-text" >Name this device</span>
|
|
29
|
+
<input class="form-control" type="text" id="device_name" name="device_name" aria-labelledby="nameHelpBlock" placeholder="e.g. {{device.__name__}}_1" >
|
|
30
|
+
<div id="nameHelpBlock" class="form-text">
|
|
31
|
+
Name your instrument, avoid names that are defined on the right
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
{% for arg in device.__init__.__annotations__ %}
|
|
35
|
+
{% if not arg == "return" %}
|
|
36
|
+
<div class="input-group mb-3">
|
|
37
|
+
<span class="input-group-text" >{{arg}}</span>
|
|
38
|
+
<input class="form-control" type="text" id="{{arg}}" name="{{arg}}"
|
|
39
|
+
placeholder="{{device.__init__.__annotations__[arg].__name__}}"
|
|
40
|
+
value="{{args.parameters[arg].default if not args.parameters[arg].default.__name__ == '_empty' else ''}}">
|
|
41
|
+
{% if device.__init__.__annotations__[arg].__module__ is not in ["builtins", "typing"] %}
|
|
42
|
+
<a role="button" href="{{ url_for('control.new_controller', instrument=device.__init__.__annotations__[arg].__name__) }}" class="btn btn-secondary">initialize {{device.__init__.__annotations__[arg].__name__}} first</a>
|
|
43
|
+
{% endif %}
|
|
44
|
+
</div>
|
|
45
|
+
{% endif %}
|
|
46
|
+
{% endfor %}
|
|
47
|
+
<button type="submit" class="btn btn-dark">Connect</button>
|
|
48
|
+
</div>
|
|
49
|
+
</form>
|
|
50
|
+
{% endif %}
|
|
51
|
+
</div>
|
|
52
|
+
<div class="col-xl-3 col-lg-3 col-md-6 mb-4">
|
|
53
|
+
<h5>Defined Instruments</h5><hr>
|
|
54
|
+
{% if defined_variables %}
|
|
55
|
+
<ul class="list-group">
|
|
56
|
+
{% for instrument in defined_variables %}
|
|
57
|
+
<li class="list-group-item">{{instrument}}</li>
|
|
58
|
+
{% endfor %}
|
|
59
|
+
</ul>
|
|
60
|
+
{% endif %}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
<div class="modal fade" id="importAPI" tabindex="-1" aria-labelledby="importModal" aria-hidden="true" >
|
|
66
|
+
<div class="modal-dialog">
|
|
67
|
+
<div class="modal-content">
|
|
68
|
+
<div class="modal-header">
|
|
69
|
+
<h1 class="modal-title fs-5" id="importModal">Import API by file path</h1>
|
|
70
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
71
|
+
</div>
|
|
72
|
+
<form method="POST" action="{{ url_for('control.import_api') }}" enctype="multipart/form-data">
|
|
73
|
+
<div class="modal-body">
|
|
74
|
+
<h5>input manually</h5>
|
|
75
|
+
<div class="input-group mb-3">
|
|
76
|
+
<label class="input-group-text" for="filepath">File Path:</label>
|
|
77
|
+
<input type="text" class="form-control" name="filepath" id="filepath">
|
|
78
|
+
</div>
|
|
79
|
+
<div class="modal-footer">
|
|
80
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button>
|
|
81
|
+
<button type="submit" class="btn btn-primary"> Save </button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</form>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{% endblock %}
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app
|
|
2
|
+
from flask_login import login_required
|
|
3
|
+
|
|
4
|
+
from ivoryos.utils.db_models import Script, db
|
|
5
|
+
from ivoryos.utils.utils import get_script_file, post_script_file
|
|
6
|
+
|
|
7
|
+
database = Blueprint('database', __name__, template_folder='templates/database')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@database.route("/delete/<id>")
|
|
11
|
+
@login_required
|
|
12
|
+
def delete_action(id):
|
|
13
|
+
back = request.referrer
|
|
14
|
+
script = get_script_file()
|
|
15
|
+
script.delete_action(id)
|
|
16
|
+
post_script_file(script)
|
|
17
|
+
return redirect(back)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@database.route("/edit_workflow/<workflow_name>")
|
|
21
|
+
@login_required
|
|
22
|
+
def edit_workflow(workflow_name):
|
|
23
|
+
row = Script.query.get(workflow_name)
|
|
24
|
+
script = Script(**row.as_dict())
|
|
25
|
+
post_script_file(script)
|
|
26
|
+
pseudo_name = session.get("pseudo_deck", "")
|
|
27
|
+
off_line = current_app.config["OFF_LINE"]
|
|
28
|
+
if off_line and pseudo_name and not script.deck == pseudo_name:
|
|
29
|
+
flash(f"Choose the deck with name {script.deck}")
|
|
30
|
+
return redirect(url_for('design.experiment_builder'))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@database.route("/delete_workflow/<workflow_name>")
|
|
34
|
+
@login_required
|
|
35
|
+
def delete_workflow(workflow_name):
|
|
36
|
+
Script.query.filter(Script.name == workflow_name).delete()
|
|
37
|
+
db.session.commit()
|
|
38
|
+
return redirect(url_for('database.load_from_database'))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@database.route("/publish")
|
|
42
|
+
@login_required
|
|
43
|
+
def publish():
|
|
44
|
+
script = get_script_file()
|
|
45
|
+
if not script.name or not script.deck:
|
|
46
|
+
flash("Deck cannot be empty, try to re-submit deck configuration on the left panel")
|
|
47
|
+
row = Script.query.get(script.name)
|
|
48
|
+
if row and row.status == "finalized":
|
|
49
|
+
flash("This is a protected script, use save as to rename.")
|
|
50
|
+
elif row and not session['user'] == row.author:
|
|
51
|
+
flash("You are not the author, use save as to rename.")
|
|
52
|
+
else:
|
|
53
|
+
db.session.merge(script)
|
|
54
|
+
db.session.commit()
|
|
55
|
+
flash("Saved!")
|
|
56
|
+
return redirect(url_for('design.experiment_builder'))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@database.route("/finalize")
|
|
60
|
+
@login_required
|
|
61
|
+
def finalize():
|
|
62
|
+
script = get_script_file()
|
|
63
|
+
script.finalize()
|
|
64
|
+
db.session.merge(script)
|
|
65
|
+
db.session.commit()
|
|
66
|
+
post_script_file(script)
|
|
67
|
+
return redirect(url_for('design.experiment_builder'))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@database.route("/database/", methods=['GET', 'POST'])
|
|
71
|
+
@database.route("/database/<deck_name>", methods=['GET', 'POST'])
|
|
72
|
+
@login_required
|
|
73
|
+
def load_from_database(deck_name=None):
|
|
74
|
+
session.pop('edit_action', None) # reset cache
|
|
75
|
+
query = Script.query
|
|
76
|
+
search_term = request.args.get("keyword", None)
|
|
77
|
+
if search_term:
|
|
78
|
+
query = query.filter(Script.name.like(f'%{search_term}%'))
|
|
79
|
+
if deck_name is None:
|
|
80
|
+
temp = Script.query.with_entities(Script.deck).distinct().all()
|
|
81
|
+
deck_list = [i[0] for i in temp]
|
|
82
|
+
else:
|
|
83
|
+
query = query.filter(Script.deck == deck_name)
|
|
84
|
+
deck_list = ["ALL"]
|
|
85
|
+
page = request.args.get('page', default=1, type=int)
|
|
86
|
+
per_page = 10
|
|
87
|
+
|
|
88
|
+
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
89
|
+
return render_template("experiment_database.html", workflows=workflows, deck_list=deck_list, deck_name=deck_name)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@database.route("/edit_run_name", methods=['GET', 'POST'])
|
|
93
|
+
@login_required
|
|
94
|
+
def edit_run_name():
|
|
95
|
+
if request.method == "POST":
|
|
96
|
+
run_name = request.form.get("run_name")
|
|
97
|
+
exist_script = Script.query.get(run_name)
|
|
98
|
+
if not exist_script:
|
|
99
|
+
script = get_script_file()
|
|
100
|
+
script.save_as(run_name)
|
|
101
|
+
post_script_file(script)
|
|
102
|
+
else:
|
|
103
|
+
flash("Script name is already exist in database")
|
|
104
|
+
return redirect(url_for("design.experiment_builder"))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@database.route("/save_as", methods=['GET', 'POST'])
|
|
108
|
+
@login_required
|
|
109
|
+
def save_as():
|
|
110
|
+
# script = get_script_file()
|
|
111
|
+
if request.method == "POST":
|
|
112
|
+
run_name = request.form.get("run_name")
|
|
113
|
+
exist_script = Script.query.get(run_name)
|
|
114
|
+
if not exist_script:
|
|
115
|
+
script = get_script_file()
|
|
116
|
+
script.save_as(run_name)
|
|
117
|
+
script.author = session.get('user')
|
|
118
|
+
post_script_file(script)
|
|
119
|
+
publish()
|
|
120
|
+
else:
|
|
121
|
+
flash("Script name is already exist in database")
|
|
122
|
+
return redirect(url_for("design.experiment_builder"))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
|
|
3
|
+
{% block title %}IvoryOS | Design Database{% endblock %}
|
|
4
|
+
{% block body %}
|
|
5
|
+
<div class="database-filter">
|
|
6
|
+
{% for deck_name in deck_list %}
|
|
7
|
+
{% if deck_name == "ALL" %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database')}}">Back</a>
|
|
8
|
+
{% else %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database',deck_name=deck_name)}}">{{deck_name}}</a>
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% 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
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<table class="table table-hover" id="workflowLibrary">
|
|
25
|
+
<thead>
|
|
26
|
+
<tr>
|
|
27
|
+
<th scope="col">Workflow name</th>
|
|
28
|
+
<th scope="col">Deck </th>
|
|
29
|
+
<th scope="col">Current status</th>
|
|
30
|
+
<th scope="col">Time created</th>
|
|
31
|
+
<th scope="col">Last modified</th>
|
|
32
|
+
<th scope="col">Author</th>
|
|
33
|
+
<th scope="col"></th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody>
|
|
37
|
+
{% for workflow in workflows %}
|
|
38
|
+
<tr>
|
|
39
|
+
<td><a href="{{ url_for('database.edit_workflow', workflow_name=workflow.name) }}">{{ workflow.name }}</a></td>
|
|
40
|
+
<td>{{ workflow.deck }}</td>
|
|
41
|
+
<td>{{ workflow.status }}</td>
|
|
42
|
+
<td>{{ workflow.time_created }}</td>
|
|
43
|
+
<td>{{ workflow.last_modified }}</td>
|
|
44
|
+
<td>{{ workflow.author }}</td>
|
|
45
|
+
<td>
|
|
46
|
+
{#not workflow.status == "finalized" or#}
|
|
47
|
+
{% if session['user'] == 'admin' or session['user'] == workflow.author %}
|
|
48
|
+
<a href="{{ url_for('database.delete_workflow', workflow_name=workflow.name) }}">delete</a>
|
|
49
|
+
{% else %}
|
|
50
|
+
<a class="disabled-link" href="{{ url_for('database.delete_workflow', workflow_name=workflow.name) }}">delete</a>
|
|
51
|
+
{% endif %}
|
|
52
|
+
<td>
|
|
53
|
+
</tr>
|
|
54
|
+
{% endfor %}
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
|
|
58
|
+
{# paging#}
|
|
59
|
+
<div class="pagination justify-content-center">
|
|
60
|
+
<div class="page-item {{ 'disabled' if not workflows.has_prev else '' }}">
|
|
61
|
+
<a class="page-link" href="{{ url_for('database.load_from_database', page=workflows.prev_num) }}">Previous</a>
|
|
62
|
+
</div>
|
|
63
|
+
{% for num in workflows.iter_pages() %}
|
|
64
|
+
<div class="page-item">
|
|
65
|
+
<a class="page-link {{ 'active' if num == workflows.page else '' }}" href="{{ url_for('database.load_from_database', page=num) }}">{{ num }}</a>
|
|
66
|
+
</div>
|
|
67
|
+
{% endfor %}
|
|
68
|
+
<div class="page-item {{ 'disabled' if not workflows.has_next else '' }}">
|
|
69
|
+
<a class="page-link" href="{{ url_for('database.load_from_database', page=workflows.next_num) }}">Next</a>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
{% endblock %}
|
|
File without changes
|