258 lines
12 KiB
HTML
258 lines
12 KiB
HTML
{% extends 'base.html' %}
|
|
{% from "macros.html" import render_field, render_selectable_table %}
|
|
|
|
{% block title %}Tune Specialist{% endblock %}
|
|
|
|
{% block content_title %}Tune Specialist{% endblock %}
|
|
{% block content_description %}Edit agents, tasks and tools for specialist <b>{{ specialist.name }}</b> (ID: {{ specialist.id }}) <br>
|
|
({{ specialist.type }} - {{ specialist.type_version }}){% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-0">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mt-3 mb-3">
|
|
<div>
|
|
<a href="{{ prefixed_url_for('interaction_bp.specialists', for_redirect=True) }}" class="btn btn-outline-secondary btn-sm">Back to Specialists</a>
|
|
<a href="{{ prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist.id, for_redirect=True) }}" class="btn btn-outline-primary btn-sm">Edit Specialist Metadata</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="card-title mb-3">Components</h6>
|
|
{# Gebruik de generieke EveAI list view template zodat we dezelfde
|
|
Tabulator + EveAI.ListView infrastructuur hergebruiken als elders.
|
|
We mappen hier de component-variabelen naar de namen die
|
|
eveai_list_view.html verwacht. #}
|
|
{% with
|
|
title=components_title,
|
|
description=components_description,
|
|
data=components_data,
|
|
columns=components_columns,
|
|
actions=components_actions,
|
|
initial_sort=components_initial_sort,
|
|
table_id=components_table_id,
|
|
table_height=components_table_height
|
|
%}
|
|
{% include 'eveai_list_view.html' %}
|
|
{% endwith %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Component Editor Modal (intentionally placed outside the main content to avoid nested forms) -->
|
|
<div class="modal fade" id="componentModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="componentModalLabel">Edit Component</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="componentModalBody">
|
|
<!-- Partial form will be injected here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
{{ super() }}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const componentModalEl = document.getElementById('componentModal');
|
|
const componentModalBody = document.getElementById('componentModalBody');
|
|
const componentModalLabel = document.getElementById('componentModalLabel');
|
|
let componentModal;
|
|
|
|
const tableId = '{{ components_table_id }}';
|
|
|
|
// Initialiseer de specialist components tabel via de generieke EveAI.ListView
|
|
function initializeComponentsTable(retriesLeft = 50) {
|
|
if (!window.EveAI || !window.EveAI.ListView || !window.EveAI.ListView.initialize) {
|
|
if (retriesLeft <= 0) {
|
|
console.error('EveAI.ListView.initialize niet beschikbaar voor components tabel');
|
|
return;
|
|
}
|
|
return setTimeout(() => initializeComponentsTable(retriesLeft - 1), 100);
|
|
}
|
|
|
|
// Bouw dezelfde configuratie op als in list_view.html, maar dan voor de components
|
|
const tableConfig = {
|
|
data: {{ components_data | tojson }},
|
|
columns: {{ components_columns | tojson }},
|
|
initialSort: {{ components_initial_sort | tojson }},
|
|
actions: {{ components_actions | tojson }},
|
|
tableHeight: {{ components_table_height|default(600) }},
|
|
selectable: true
|
|
};
|
|
|
|
const tabulatorTable = window.EveAI.ListView.initialize(tableId, tableConfig);
|
|
if (!tabulatorTable) {
|
|
console.error('Kon de Tabulator tabel voor specialist components niet initialiseren');
|
|
return;
|
|
}
|
|
|
|
console.log('Specialist components Tabulator tabel succesvol geïnitialiseerd');
|
|
}
|
|
|
|
// Registreer de pagina-specifieke embedded handler voor de actie 'edit_component'
|
|
function registerEmbeddedHandler(retriesLeft = 50) {
|
|
if (!window.EveAI || !window.EveAI.ListView || !window.EveAI.ListView.instances) {
|
|
if (retriesLeft <= 0) {
|
|
console.error('EveAI.ListView.instances niet beschikbaar voor components tabel');
|
|
return;
|
|
}
|
|
return setTimeout(() => registerEmbeddedHandler(retriesLeft - 1), 100);
|
|
}
|
|
|
|
const instances = window.EveAI.ListView.instances;
|
|
if (!instances[tableId] || !instances[tableId].table) {
|
|
if (retriesLeft <= 0) {
|
|
console.error(`Geen EveAI.ListView instance gevonden voor tabel ${tableId}`);
|
|
return;
|
|
}
|
|
return setTimeout(() => registerEmbeddedHandler(retriesLeft - 1), 100);
|
|
}
|
|
|
|
window.EveAI.ListView.embeddedHandlers = window.EveAI.ListView.embeddedHandlers || {};
|
|
|
|
window.EveAI.ListView.embeddedHandlers[tableId] = function(action, row, tId) {
|
|
if (action !== 'edit_component' || !row) return;
|
|
const id = row.id;
|
|
const type = row.type_name; // 'agent' | 'task' | 'tool'
|
|
|
|
// Build edit URL from server-side templates with placeholder 0
|
|
const editUrls = {
|
|
agent: "{{ prefixed_url_for('interaction_bp.edit_agent', agent_id=0) }}",
|
|
task: "{{ prefixed_url_for('interaction_bp.edit_task', task_id=0) }}",
|
|
tool: "{{ prefixed_url_for('interaction_bp.edit_tool', tool_id=0) }}",
|
|
};
|
|
const url = (editUrls[type] || '').replace('/0', `/${id}`);
|
|
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
|
.then(resp => { if (!resp.ok) throw new Error(`HTTP ${resp.status}`); return resp.text(); })
|
|
.then(html => {
|
|
componentModalBody.innerHTML = html;
|
|
componentModalLabel.textContent = `Edit ${type.charAt(0).toUpperCase()+type.slice(1)}`;
|
|
componentModalEl.dataset.componentType = type;
|
|
componentModalEl.dataset.componentId = id;
|
|
|
|
// Helper om modal te openen zodra Bootstrap beschikbaar is
|
|
function openModal() {
|
|
try {
|
|
if (!componentModal) componentModal = new bootstrap.Modal(componentModalEl);
|
|
componentModal.show();
|
|
} catch (e) {
|
|
console.error('Failed to open Bootstrap modal:', e);
|
|
alert('Kan de editor niet openen (Bootstrap modal ontbreekt).');
|
|
}
|
|
}
|
|
|
|
if (window.bootstrap && typeof bootstrap.Modal === 'function') {
|
|
openModal();
|
|
} else {
|
|
// Fallback: laad Bootstrap bundle dynamisch en open daarna
|
|
const script = document.createElement('script');
|
|
script.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js';
|
|
script.onload = () => openModal();
|
|
script.onerror = () => {
|
|
console.error('Kon Bootstrap bundle niet laden');
|
|
alert('Kan de editor niet openen omdat Bootstrap JS ontbreekt.');
|
|
};
|
|
document.head.appendChild(script);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('Error loading editor:', err);
|
|
alert('Error loading editor. Please try again.');
|
|
});
|
|
};
|
|
}
|
|
|
|
// Eerst de tabel initialiseren, daarna de embedded handler registreren
|
|
initializeComponentsTable();
|
|
registerEmbeddedHandler();
|
|
|
|
// Refresh de components list data na een succesvolle save
|
|
function refreshComponentsData() {
|
|
const url = "{{ prefixed_url_for('interaction_bp.specialist_components_data', specialist_id=specialist_id) }}";
|
|
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
|
|
.then(resp => resp.json())
|
|
.then(payload => {
|
|
if (!window.EveAI || !window.EveAI.ListView || !window.EveAI.ListView.instances) return;
|
|
const instance = window.EveAI.ListView.instances[tableId];
|
|
if (instance && instance.table && Array.isArray(payload.data)) {
|
|
instance.table.setData(payload.data);
|
|
}
|
|
})
|
|
.catch(err => console.error('Failed to refresh components data', err));
|
|
}
|
|
|
|
// Intercepteer form submits binnen de modal (ook Enter-key)
|
|
componentModalBody.addEventListener('submit', function(e) {
|
|
const formEl = e.target.closest('#componentEditForm');
|
|
if (!formEl) return; // Niet ons form
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(formEl);
|
|
|
|
const componentType = componentModalEl.dataset.componentType;
|
|
const componentId = componentModalEl.dataset.componentId;
|
|
|
|
// Bouw prefix-bewuste absolute save URL op basis van server-side templates
|
|
const saveUrls = {
|
|
agent: "{{ prefixed_url_for('interaction_bp.save_agent', agent_id=0) }}",
|
|
task: "{{ prefixed_url_for('interaction_bp.save_task', task_id=0) }}",
|
|
tool: "{{ prefixed_url_for('interaction_bp.save_tool', tool_id=0) }}",
|
|
};
|
|
const urlTemplate = saveUrls[componentType];
|
|
const saveUrl = urlTemplate.replace('/0', `/${componentId}`);
|
|
|
|
fetch(saveUrl, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
})
|
|
.then(async resp => {
|
|
const ct = resp.headers.get('Content-Type') || '';
|
|
if (resp.ok) {
|
|
// Verwacht JSON succes
|
|
let data = null;
|
|
try { data = await resp.json(); } catch (_) {}
|
|
if (data && data.success) {
|
|
if (componentModal) {
|
|
componentModal.hide();
|
|
}
|
|
refreshComponentsData();
|
|
return;
|
|
}
|
|
throw new Error(data && data.message ? data.message : 'Save failed');
|
|
}
|
|
// Validatiefouten (400) → server stuurt HTML partial terug
|
|
if (resp.status === 400 && ct.includes('text/html')) {
|
|
const html = await resp.text();
|
|
componentModalBody.innerHTML = html;
|
|
return;
|
|
}
|
|
// Andere fouten
|
|
let message = 'Save failed';
|
|
if (ct.includes('application/json')) {
|
|
try {
|
|
const data = await resp.json();
|
|
if (data && data.message) message = data.message;
|
|
} catch (_) {}
|
|
}
|
|
throw new Error(message + ` (HTTP ${resp.status})`);
|
|
})
|
|
.catch(err => {
|
|
console.error('Error saving component:', err);
|
|
alert(err.message || 'Error saving component');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|