forked from enviPath/enviPy
[Feature] PEPPER in enviPath (#332)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#332
This commit is contained in:
@ -43,14 +43,12 @@
|
||||
class="select select-bordered w-full"
|
||||
:class="{ 'select-error': $store.validationErrors.hasError(fieldName, context) }"
|
||||
x-model="value"
|
||||
:multiple="multiple"
|
||||
>
|
||||
<option value="" :selected="!value">Select...</option>
|
||||
|
||||
<template x-for="opt in options" :key="opt">
|
||||
<option
|
||||
:value="opt"
|
||||
:selected="value === opt"
|
||||
x-text="opt"
|
||||
></option>
|
||||
<option :value="opt" x-text="opt"></option>
|
||||
</template>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
isSubmitting: false,
|
||||
modelType: '',
|
||||
buildAppDomain: false,
|
||||
requiresRulePackages: false,
|
||||
requiresDataPackages: false,
|
||||
|
||||
reset() {
|
||||
this.isSubmitting = false;
|
||||
@ -24,6 +26,21 @@
|
||||
return this.modelType === 'enviformer';
|
||||
},
|
||||
|
||||
get showRulePackages() {
|
||||
console.log(this.requiresRulePackages);
|
||||
return this.requiresRulePackages;
|
||||
},
|
||||
|
||||
get showDataPackages() {
|
||||
return this.requiresDataPackages;
|
||||
},
|
||||
|
||||
updateRequirements(event) {
|
||||
const option = event.target.selectedOptions[0];
|
||||
this.requiresRulePackages = option.dataset.requires_rule_packages === 'True';
|
||||
this.requiresDataPackages = option.dataset.requires_data_packages === 'True';
|
||||
},
|
||||
|
||||
submit(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (form && form.checkValidity()) {
|
||||
@ -111,17 +128,24 @@
|
||||
name="model-type"
|
||||
class="select select-bordered w-full"
|
||||
x-model="modelType"
|
||||
x-on:change="updateRequirements($event)"
|
||||
required
|
||||
>
|
||||
<option value="" disabled selected>Select Model Type</option>
|
||||
{% for k, v in model_types.items %}
|
||||
<option value="{{ v }}">{{ k }}</option>
|
||||
<option
|
||||
value="{{ v.type }}"
|
||||
data-requires_rule_packages="{{ v.requires_rule_packages }}"
|
||||
data-requires_data_packages="{{ v.requires_data_packages }}"
|
||||
>
|
||||
{{ k }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Rule Packages (MLRR, RBRR) -->
|
||||
<div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
|
||||
<div class="form-control mb-3" x-show="showRulePackages" x-cloak>
|
||||
<label class="label" for="model-rule-packages">
|
||||
<span class="label-text">Rule Packages</span>
|
||||
</label>
|
||||
@ -152,11 +176,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Data Packages (MLRR, RBRR, Enviformer) -->
|
||||
<div
|
||||
class="form-control mb-3"
|
||||
x-show="showMlrr || showRbrr || showEnviformer"
|
||||
x-cloak
|
||||
>
|
||||
<div class="form-control mb-3" x-show="showDataPackages" x-cloak>
|
||||
<label class="label" for="model-data-packages">
|
||||
<span class="label-text">Data Packages</span>
|
||||
</label>
|
||||
|
||||
@ -233,6 +233,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if property_models %}
|
||||
<div class="form-control mb-3">
|
||||
<label class="label" for="prediction-setting-property-models">
|
||||
<span class="label-text">Select Property Models</span>
|
||||
</label>
|
||||
<select
|
||||
id="prediction-setting-property-models"
|
||||
name="prediction-setting-property-models"
|
||||
class="select select-bordered w-full"
|
||||
multiple
|
||||
>
|
||||
<option value="" disabled selected>Select the model</option>
|
||||
{% for pm in property_models %}
|
||||
<option value="{{ pm.url }}">{{ pm.name|safe }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
|
||||
144
templates/objects/model/_model_base.html
Normal file
144
templates/objects/model/_model_base.html
Normal file
@ -0,0 +1,144 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% load static %}
|
||||
{% load envipytags %}
|
||||
{% block content %}
|
||||
|
||||
{% block action_modals %}
|
||||
{% include "modals/objects/edit_model_modal.html" %}
|
||||
{% include "modals/objects/evaluate_model_modal.html" %}
|
||||
{% include "modals/objects/retrain_model_modal.html" %}
|
||||
{% include "modals/objects/generic_delete_modal.html" %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
{% block libraries %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="space-y-2 p-4">
|
||||
<!-- Header Section -->
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="card-title text-2xl">{{ model.name }}</h2>
|
||||
<div id="actionsButton" class="dropdown dropdown-end hidden">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-wrench"
|
||||
>
|
||||
<path
|
||||
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||
/>
|
||||
</svg>
|
||||
Actions
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||
>
|
||||
{% block actions %}
|
||||
{% include "actions/objects/model.html" %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2">{{ model.description|safe }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Status -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Model Status</div>
|
||||
<div class="collapse-content">{{ model.status }}</div>
|
||||
</div>
|
||||
|
||||
{% block packages %}
|
||||
{% if model.rule_packages.all|length > 0 %}
|
||||
<!-- Rule Packages -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Rule Packages</div>
|
||||
<div class="collapse-content">
|
||||
<ul class="menu bg-base-100 rounded-box w-full">
|
||||
{% for p in model.rule_packages.all %}
|
||||
<li>
|
||||
<a href="{{ p.url }}" class="hover:bg-base-200"
|
||||
>{{ p.name }}</a
|
||||
>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if model.data_packages.all|length > 0 %}
|
||||
<!-- Reaction Packages -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Reaction Packages
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<ul class="menu bg-base-100 rounded-box w-full">
|
||||
{% for p in model.data_packages.all %}
|
||||
<li>
|
||||
<a href="{{ p.url }}" class="hover:bg-base-200"
|
||||
>{{ p.name }}</a
|
||||
>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if model.eval_packages.all|length > 0 %}
|
||||
<!-- Eval Packages -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Eval Packages</div>
|
||||
<div class="collapse-content">
|
||||
<ul class="menu bg-base-100 rounded-box w-full">
|
||||
{% for p in model.eval_packages.all %}
|
||||
<li>
|
||||
<a href="{{ p.url }}" class="hover:bg-base-200"
|
||||
>{{ p.name }}</a
|
||||
>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block usemodel %}
|
||||
{% endblock %}
|
||||
|
||||
{% block evaluation %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<script>
|
||||
function makeLoadingGif(selector, gifPath) {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
element.innerHTML = '<img src="' + gifPath + '" alt="Loading...">';
|
||||
}
|
||||
}
|
||||
|
||||
function clear(divid) {
|
||||
const element = document.getElementById(divid);
|
||||
if (element) {
|
||||
element.classList.remove("alert", "alert-error");
|
||||
element.innerHTML = "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock content %}
|
||||
430
templates/objects/model/classification_model.html
Normal file
430
templates/objects/model/classification_model.html
Normal file
@ -0,0 +1,430 @@
|
||||
{% extends "objects/model/_model_base.html" %}
|
||||
{% load static %}
|
||||
{% load envipytags %}
|
||||
|
||||
{% block libraries %}
|
||||
<!-- Include required libs -->
|
||||
<script src="https://d3js.org/d3.v5.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.js"></script>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
{% endblock %}
|
||||
|
||||
{% block usemodel %}
|
||||
{% if model.ready_for_prediction %}
|
||||
<!-- Predict Panel -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium" id="predictTitle">
|
||||
Predict
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="form-control">
|
||||
<div class="join w-full">
|
||||
<input
|
||||
id="smiles-to-predict"
|
||||
type="text"
|
||||
class="input input-bordered join-item grow"
|
||||
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary join-item"
|
||||
type="button"
|
||||
id="predict-button"
|
||||
>
|
||||
Predict!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="predictLoading" class="mt-2 flex hidden justify-center">
|
||||
<div class="h-8 w-8">
|
||||
{% include "components/loading-spinner.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="predictResultTable" class="mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if model.ready_for_prediction and model.app_domain %}
|
||||
<!-- App Domain -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Applicability Domain Assessment
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="form-control">
|
||||
<div class="join w-full">
|
||||
<input
|
||||
id="smiles-to-assess"
|
||||
type="text"
|
||||
class="input input-bordered join-item grow"
|
||||
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary join-item"
|
||||
type="button"
|
||||
id="assess-button"
|
||||
>
|
||||
Assess!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="appDomainLoading" class="mt-2 flex hidden justify-center">
|
||||
<div class="h-8 w-8">
|
||||
{% include "components/loading-spinner.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="appDomainAssessmentResultTable" class="mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function handlePredictionResponse(data) {
|
||||
let stereo = data["stereo"];
|
||||
data = data["pred"];
|
||||
let res = "";
|
||||
if (stereo) {
|
||||
res +=
|
||||
"<span class='alert alert-warning alert-soft'>Removed stereochemistry for prediction</span><br>";
|
||||
}
|
||||
res += "<table class='table table-zebra'>";
|
||||
res += "<thead>";
|
||||
res += "<th scope='col'>#</th>";
|
||||
|
||||
const columns = ["products", "image", "probability", "btrule"];
|
||||
|
||||
for (const col of columns) {
|
||||
res += "<th scope='col'>" + col + "</th>";
|
||||
}
|
||||
res += "</thead>";
|
||||
res += "<tbody>";
|
||||
let cnt = 1;
|
||||
for (const transformation in data) {
|
||||
res += "<tr>";
|
||||
res += "<th scope='row'>" + cnt + "</th>";
|
||||
res +=
|
||||
"<th scope='row'>" +
|
||||
data[transformation]["products"][0].join(", ") +
|
||||
"</th>";
|
||||
res +=
|
||||
"<th scope='row'>" +
|
||||
"<img width='400' src='{% url 'depict' %}?smiles=" +
|
||||
encodeURIComponent(data[transformation]["products"][0].join(".")) +
|
||||
"'></th>";
|
||||
res +=
|
||||
"<th scope='row'>" +
|
||||
data[transformation]["probability"].toFixed(3) +
|
||||
"</th>";
|
||||
if (data[transformation]["btrule"] != null) {
|
||||
res +=
|
||||
"<th scope='row'>" +
|
||||
"<a href='" +
|
||||
data[transformation]["btrule"]["url"] +
|
||||
"' class='link link-primary'>" +
|
||||
data[transformation]["btrule"]["name"] +
|
||||
"</a>" +
|
||||
"</th>";
|
||||
} else {
|
||||
res += "<th scope='row'>N/A</th>";
|
||||
}
|
||||
res += "</tr>";
|
||||
cnt += 1;
|
||||
}
|
||||
|
||||
res += "</tbody>";
|
||||
res += "</table>";
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.innerHTML = res;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Show actions button if there are actions
|
||||
const actionsButton = document.getElementById("actionsButton");
|
||||
const actionsList = actionsButton?.querySelector("ul");
|
||||
if (actionsList && actionsList.children.length > 0) {
|
||||
actionsButton?.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// Predict button handler
|
||||
const predictButton = document.getElementById("predict-button");
|
||||
if (predictButton) {
|
||||
predictButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clear("predictResultTable");
|
||||
|
||||
const smilesInput = document.getElementById("smiles-to-predict");
|
||||
const smiles = smilesInput ? smilesInput.value.trim() : "";
|
||||
|
||||
if (smiles === "") {
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
"Please enter a SMILES string to predict!";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) loadingEl.classList.remove("hidden");
|
||||
|
||||
const params = new URLSearchParams({
|
||||
smiles: smiles,
|
||||
classify: "ILikeCats!",
|
||||
});
|
||||
|
||||
fetch("?" + params.toString(), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-CSRFToken":
|
||||
document.querySelector("[name=csrf-token]").content,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
handlePredictionResponse(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
error.error || "Error while processing response :/";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Assess button handler
|
||||
const assessButton = document.getElementById("assess-button");
|
||||
if (assessButton) {
|
||||
assessButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clear("appDomainAssessmentResultTable");
|
||||
|
||||
const smilesInput = document.getElementById("smiles-to-assess");
|
||||
const smiles = smilesInput ? smilesInput.value.trim() : "";
|
||||
|
||||
if (smiles === "") {
|
||||
const resultTable = document.getElementById(
|
||||
"appDomainAssessmentResultTable",
|
||||
);
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
"Please enter a SMILES string to predict!";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const loadingEl = document.getElementById("appDomainLoading");
|
||||
if (loadingEl) loadingEl.classList.remove("hidden");
|
||||
|
||||
const params = new URLSearchParams({
|
||||
smiles: smiles,
|
||||
"app-domain-assessment": "ILikeCats!",
|
||||
});
|
||||
|
||||
fetch("?" + params.toString(), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-CSRFToken":
|
||||
document.querySelector("[name=csrf-token]").content,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const loadingEl = document.getElementById("appDomainLoading");
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
if (typeof handleAssessmentResponse === "function") {
|
||||
handleAssessmentResponse("{% url 'depict' %}", data);
|
||||
}
|
||||
console.log(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
const loadingEl = document.getElementById("appDomainLoading");
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
const resultTable = document.getElementById(
|
||||
"appDomainAssessmentResultTable",
|
||||
);
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
error.error || "Error while processing response :/";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block evaluation %}
|
||||
{# prettier-ignore-start #}
|
||||
{% if model.model_status == 'FINISHED' %}
|
||||
<!-- Single Gen Curve Panel -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked/>
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Precision Recall Curve
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="flex justify-center">
|
||||
<div id="sg-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if model.multigen_eval %}
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked/>
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Multi Gen Precision Recall Curve
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="flex justify-center">
|
||||
<div id="mg-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<script>
|
||||
function makeChart(selector, data) {
|
||||
const x = ['Recall'];
|
||||
const y = ['Precision'];
|
||||
const thres = ['threshold'];
|
||||
|
||||
function compare(a, b) {
|
||||
if (a.threshold < b.threshold)
|
||||
return -1;
|
||||
else if (a.threshold > b.threshold)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getIndexForValue(data, val, val_name) {
|
||||
for (const idx in data) {
|
||||
if (data[idx][val_name] == val) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
console.warn('PR curve data is empty');
|
||||
return;
|
||||
}
|
||||
const dataLength = data.length;
|
||||
data.sort(compare);
|
||||
|
||||
for (const idx in data) {
|
||||
const d = data[idx];
|
||||
x.push(d.recall);
|
||||
y.push(d.precision);
|
||||
thres.push(d.threshold);
|
||||
}
|
||||
const chart = c3.generate({
|
||||
bindto: selector,
|
||||
data: {
|
||||
onclick: function (d, e) {
|
||||
const idx = d.index;
|
||||
const thresh = data[dataLength - idx - 1].threshold;
|
||||
},
|
||||
x: 'Recall',
|
||||
y: 'Precision',
|
||||
columns: [
|
||||
x,
|
||||
y,
|
||||
]
|
||||
},
|
||||
size: {
|
||||
height: 400,
|
||||
width: 480
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
label: 'Recall',
|
||||
padding: 0,
|
||||
tick: {
|
||||
fit: true,
|
||||
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
||||
}
|
||||
},
|
||||
y: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
label: 'Precision',
|
||||
padding: 0,
|
||||
tick: {
|
||||
fit: true,
|
||||
values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
||||
}
|
||||
}
|
||||
},
|
||||
point: {
|
||||
r: 4
|
||||
},
|
||||
tooltip: {
|
||||
format: {
|
||||
title: function (recall) {
|
||||
const idx = getIndexForValue(data, recall, "recall");
|
||||
if (idx != -1) {
|
||||
return "Threshold: " + data[idx].threshold;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
value: function (precision, ratio, id) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
{% if model.model_status == 'FINISHED' %}
|
||||
// Precision Recall Curve
|
||||
makeChart('#sg-chart', {{ model.pr_curve|safe }});
|
||||
{% if model.multigen_eval %}
|
||||
// Multi Gen Precision Recall Curve
|
||||
makeChart('#mg-chart', {{ model.mg_pr_curve|safe }});
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
});
|
||||
</script>
|
||||
{# prettier-ignore-end #}
|
||||
{% endblock %}
|
||||
168
templates/objects/model/property_model.html
Normal file
168
templates/objects/model/property_model.html
Normal file
@ -0,0 +1,168 @@
|
||||
{% extends "objects/model/_model_base.html" %}
|
||||
{% load static %}
|
||||
{% load envipytags %}
|
||||
|
||||
{% block libraries %}
|
||||
{% endblock %}
|
||||
|
||||
{% block usemodel %}
|
||||
|
||||
{% if model.ready_for_prediction %}
|
||||
<!-- Predict Panel -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium" id="predictTitle">
|
||||
Predict
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div class="form-control">
|
||||
<div class="join w-full">
|
||||
<input
|
||||
id="smiles-to-predict"
|
||||
type="text"
|
||||
class="input input-bordered join-item grow"
|
||||
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary join-item"
|
||||
type="button"
|
||||
id="predict-button"
|
||||
>
|
||||
Predict!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="predictLoading" class="mt-2 flex hidden justify-center">
|
||||
<div class="h-8 w-8">
|
||||
{% include "components/loading-spinner.html" %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="predictResultTable" class="mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Show actions button if there are actions
|
||||
const actionsButton = document.getElementById("actionsButton");
|
||||
const actionsList = actionsButton?.querySelector("ul");
|
||||
if (actionsList && actionsList.children.length > 0) {
|
||||
actionsButton?.classList.remove("hidden");
|
||||
}
|
||||
// Predict button handler
|
||||
const predictButton = document.getElementById("predict-button");
|
||||
if (predictButton) {
|
||||
predictButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
clear("predictResultTable");
|
||||
const smilesInput = document.getElementById("smiles-to-predict");
|
||||
const smiles = smilesInput ? smilesInput.value.trim() : "";
|
||||
|
||||
if (smiles === "") {
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
"Please enter a SMILES string to predict!";
|
||||
}
|
||||
return;
|
||||
}
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) loadingEl.classList.remove("hidden");
|
||||
const params = new URLSearchParams({
|
||||
smiles: smiles,
|
||||
half_life: "ILikeCats!",
|
||||
});
|
||||
fetch("?" + params.toString(), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-CSRFToken":
|
||||
document.querySelector("[name=csrf-token]").content,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) {
|
||||
loadingEl.classList.add("hidden");
|
||||
}
|
||||
if (data.svg === null) {
|
||||
document.getElementById("predictResultTable").innerHTML =
|
||||
"<span class='alert alert-error alert-soft'>Processing failed...</span><br>";
|
||||
return;
|
||||
}
|
||||
handlePredictionResponse(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
const loadingEl = document.getElementById("predictLoading");
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.classList.add("alert", "alert-error");
|
||||
resultTable.innerHTML =
|
||||
error.error || "Error while processing response :/";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function handlePredictionResponse(data) {
|
||||
let stereo = data["stereo"];
|
||||
data = data["svg"];
|
||||
let res = "";
|
||||
if (stereo) {
|
||||
res +=
|
||||
"<span class='alert alert-warning'>Removed stereochemistry for prediction</span><br>";
|
||||
}
|
||||
res += "<div class='flex justify-center'>" + data + "<\div>";
|
||||
const resultTable = document.getElementById("predictResultTable");
|
||||
if (resultTable) {
|
||||
resultTable.innerHTML = res;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block evaluation %}
|
||||
{% if model.model_status == 'FINISHED' %}
|
||||
<!-- Model Statistics Panel -->
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Model Statistics</div>
|
||||
<div class="collapse-content">
|
||||
<div class="flex justify-center">
|
||||
<div
|
||||
id="model-stats"
|
||||
class="overflow-x-auto rounded-box shadow-md bg-base-100"
|
||||
>
|
||||
<table class="table table-fixed w-full">
|
||||
<thead class="text-base">
|
||||
<tr>
|
||||
<th class="w-1/5">Metric</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for metric, value in model.eval_results.items %}
|
||||
<tr>
|
||||
<td>{{ metric|upper }}</td>
|
||||
<td>{{ value|floatformat:4 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@ -160,7 +160,7 @@
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-60 p-2"
|
||||
>
|
||||
{% if pathway.setting.model.app_domain %}
|
||||
<li>
|
||||
@ -206,6 +206,37 @@
|
||||
OECD 301F View
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="pred-prop-toggle-button" class="cursor-pointer">
|
||||
<svg
|
||||
id="pred-prop-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="slash"
|
||||
viewBox="0 0 100 30"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<line x1="0" y1="30" x2="100" y2="0" />
|
||||
</svg>
|
||||
Show Predicted Properties
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -441,6 +472,8 @@
|
||||
var appDomainViewEnabled = false;
|
||||
// Global switch for timeseries view
|
||||
var timeseriesViewEnabled = false;
|
||||
// Predicted Property View
|
||||
var predictedPropertyViewEnabled = false;
|
||||
|
||||
function goFullscreen(id) {
|
||||
var element = document.getElementById(id);
|
||||
@ -563,6 +596,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Predicted Propertes toggle
|
||||
const predPropBtn = document.getElementById("pred-prop-toggle-button");
|
||||
if (predPropBtn) {
|
||||
predPropBtn.addEventListener("click", function () {
|
||||
predictedPropertyViewEnabled = !predictedPropertyViewEnabled;
|
||||
const icon = document.getElementById("pred-prop-icon");
|
||||
|
||||
if (predictedPropertyViewEnabled) {
|
||||
icon.innerHTML +=
|
||||
'<svg class="slash" viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><line x1="0" y1="30" x2="100" y2="0"/></svg>';
|
||||
} else {
|
||||
icon.innerHTML =
|
||||
'<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show actions button if there are actions
|
||||
const actionsButton = document.getElementById("actionsButton");
|
||||
const actionsList = actionsButton?.querySelector("ul");
|
||||
|
||||
@ -123,7 +123,64 @@
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template x-for="item in items" :key="item.uuid">
|
||||
<template
|
||||
x-for="item in items.filter(i => i.attach_object === null)"
|
||||
:key="item.uuid"
|
||||
>
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div
|
||||
class="flex-1"
|
||||
x-data="schemaRenderer({
|
||||
rjsf: schemas[item.type.toLowerCase()],
|
||||
data: item.data,
|
||||
mode: 'view'
|
||||
})"
|
||||
x-init="init()"
|
||||
>
|
||||
{% include "components/schema_form.html" %}
|
||||
</div>
|
||||
{% if meta.can_edit %}
|
||||
<button
|
||||
class="btn btn-sm btn-ghost ml-2"
|
||||
@click="deleteItem(item.uuid)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-trash"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
x-if="items.filter(i => i.attach_object !== null).length !== 0"
|
||||
>
|
||||
<h4 class="card-title mb-4 text-lg">
|
||||
Additional Information that are attached to objects referring
|
||||
to this Scenario
|
||||
</h4>
|
||||
</template>
|
||||
|
||||
<template
|
||||
x-for="item in items.filter(i => i.attach_object !== null)"
|
||||
:key="item.uuid"
|
||||
>
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
@ -171,82 +228,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if scenario.parent %}
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title mb-4 text-lg">
|
||||
Parent Scenario Additional Information
|
||||
</h3>
|
||||
<div
|
||||
x-data="{
|
||||
items: [],
|
||||
schemas: {},
|
||||
loading: true,
|
||||
error: null,
|
||||
async init() {
|
||||
try {
|
||||
// Use the unified API client for loading data
|
||||
const { items, schemas } = await window.AdditionalInformationApi.loadSchemasAndItems('{{ scenario.parent.uuid }}');
|
||||
this.items = items;
|
||||
this.schemas = schemas;
|
||||
} catch (err) {
|
||||
this.error = err.message;
|
||||
console.error('Error loading additional information:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}"
|
||||
>
|
||||
<!-- Loading state -->
|
||||
<template x-if="loading">
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Error state -->
|
||||
<template x-if="error">
|
||||
<div class="alert alert-error mb-4">
|
||||
<span x-text="error"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Items list -->
|
||||
<template x-if="!loading && !error">
|
||||
<div class="space-y-4">
|
||||
<template x-if="items.length === 0">
|
||||
<p class="text-base-content/60">
|
||||
No additional information available.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template x-for="item in items" :key="item.uuid">
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div
|
||||
class="flex-1"
|
||||
x-data="schemaRenderer({
|
||||
rjsf: schemas[item.type.toLowerCase()],
|
||||
data: item.data,
|
||||
mode: 'view'
|
||||
})"
|
||||
x-init="init()"
|
||||
>
|
||||
{% include "components/schema_form.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pathways -->
|
||||
{% if scenario.related_pathways %}
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
@ -265,43 +246,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Related Scenarios -->
|
||||
{% if children.exists %}
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">Related Scenarios</div>
|
||||
<div class="collapse-content">
|
||||
<ul class="menu bg-base-100 rounded-box">
|
||||
{% for s in children %}
|
||||
<li>
|
||||
<a href="{{ s.url }}" class="hover:bg-base-200"
|
||||
>{{ s.name }} <i>({{ s.package.name }})</i></a
|
||||
>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Parent Scenarios -->
|
||||
{% if scenario.parent %}
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">Parent Scenario</div>
|
||||
<div class="collapse-content">
|
||||
<ul class="menu bg-base-100 rounded-box">
|
||||
<li>
|
||||
<a href="{{ scenario.parent.url }}" class="hover:bg-base-200"
|
||||
>{{ scenario.parent.name }}
|
||||
<i>({{ scenario.parent.package.name }})</i></a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@ -87,6 +87,39 @@
|
||||
<td>Expansion Scheme</td>
|
||||
<td>{{ setting_to_render.expansion_scheme }}</td>
|
||||
</tr>
|
||||
|
||||
{% if setting_to_render.property_models.all %}
|
||||
<tr>
|
||||
{% for prop_model in setting_to_render.property_models.all %}
|
||||
<td>Property Models</td>
|
||||
<td>
|
||||
<div class="space-y-2">
|
||||
<table class="table-xs table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property Type</th>
|
||||
<th>Model</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ prop_model.instance.display }}</td>
|
||||
<td>
|
||||
<a
|
||||
href="{{ prop_model.url }}"
|
||||
class="link link-primary"
|
||||
>
|
||||
{{ prop_model.name }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user