From 3815399a7efe05b3f2bb8f156aacf9ebccf9e9a8 Mon Sep 17 00:00:00 2001 From: Josako Date: Mon, 24 Nov 2025 15:54:47 +0100 Subject: [PATCH] - Specialist Tuning now in a separate editor - typeBadge formatter completed --- config/static-manifest/manifest.json | 2 +- .../assets/css/eveai-content-viewer.css | 30 + eveai_app/static/assets/css/eveai.css | 16 + eveai_app/static/assets/css/nucleo-icons.css | 597 ++++++++++++++++++ eveai_app/static/assets/css/nucleo-svg.css | 135 ++++ eveai_app/templates/eveai_list_view.html | 83 ++- .../interaction/edit_specialist.html | 343 +--------- .../interaction/tune_specialist.html | 257 ++++++++ eveai_app/views/interaction_views.py | 46 +- .../list_views/interaction_list_views.py | 1 + 10 files changed, 1132 insertions(+), 378 deletions(-) create mode 100644 eveai_app/static/assets/css/eveai-content-viewer.css create mode 100644 eveai_app/static/assets/css/nucleo-icons.css create mode 100644 eveai_app/static/assets/css/nucleo-svg.css create mode 100644 eveai_app/templates/interaction/tune_specialist.html diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index 08309b1..0bb1b16 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -2,5 +2,5 @@ "dist/chat-client.js": "dist/chat-client.f7134231.js", "dist/chat-client.css": "dist/chat-client.99e10656.css", "dist/main.js": "dist/main.6a617099.js", - "dist/main.css": "dist/main.06893f70.css" + "dist/main.css": "dist/main.7182aac3.css" } \ No newline at end of file diff --git a/eveai_app/static/assets/css/eveai-content-viewer.css b/eveai_app/static/assets/css/eveai-content-viewer.css new file mode 100644 index 0000000..f4f500c --- /dev/null +++ b/eveai_app/static/assets/css/eveai-content-viewer.css @@ -0,0 +1,30 @@ +/* Content Viewer specific styles */ + +/* Ensure markdown text aligns left by default */ +#content-document-viewer .markdown-body, +.markdown-body { + text-align: left; +} + +/* Make the viewer pane scrollable with a responsive max height */ +#content-document-viewer { + max-height: 60vh; + overflow: auto; +} + +/* Optional readability improvements */ +#content-document-viewer .markdown-body { + line-height: 1.6; +} +#content-document-viewer .markdown-body pre, +#content-document-viewer .markdown-body code { + white-space: pre-wrap; +} + +/* Keep the viewer header visible while scrolling */ +#content-viewer-section .card-header { + position: sticky; + top: 0; + z-index: 1; + background: var(--bs-body-bg, #fff); +} diff --git a/eveai_app/static/assets/css/eveai.css b/eveai_app/static/assets/css/eveai.css index 7c3614a..f7e61e6 100644 --- a/eveai_app/static/assets/css/eveai.css +++ b/eveai_app/static/assets/css/eveai.css @@ -313,5 +313,21 @@ input[type="radio"] { text-align: center !important; } +/* Custom badge background colors for type badges (agent/task/tool) */ +.bg-purple { + background-color: #6f42c1 !important; /* Bootstrap purple */ + color: #fff !important; +} + +.bg-orange { + background-color: #fd7e14 !important; /* Bootstrap orange */ + color: #fff !important; +} + +.bg-teal { + background-color: #20c997 !important; /* Bootstrap teal */ + color: #fff !important; +} + diff --git a/eveai_app/static/assets/css/nucleo-icons.css b/eveai_app/static/assets/css/nucleo-icons.css new file mode 100644 index 0000000..d77d1db --- /dev/null +++ b/eveai_app/static/assets/css/nucleo-icons.css @@ -0,0 +1,597 @@ +/*-------------------------------- + +hermes-dashboard-icons Web Font - built using nucleoapp.com +License - nucleoapp.com/license/ + +-------------------------------- */ +@font-face { + font-family: 'NucleoIcons'; + src: url('../fonts/nucleo-icons.eot'); + src: url('../fonts/nucleo-icons.eot') format('embedded-opentype'), url('../fonts/nucleo-icons.woff2') format('woff2'), url('../fonts/nucleo-icons.woff') format('woff'), url('../fonts/nucleo-icons.ttf') format('truetype'), url('../fonts/nucleo-icons.svg') format('svg'); + font-weight: normal; + font-style: normal; +} + +/*------------------------ + base class definition +-------------------------*/ +.ni { + display: inline-block; + font: normal normal normal 14px/1 NucleoIcons; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/*------------------------ + change icon size +-------------------------*/ +.ni-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} + +.ni-2x { + font-size: 2em; +} + +.ni-3x { + font-size: 3em; +} + +.ni-4x { + font-size: 4em; +} + +.ni-5x { + font-size: 5em; +} + +/*---------------------------------- + add a square/circle background +-----------------------------------*/ +.ni.square, +.ni.circle { + padding: 0.33333333em; + vertical-align: -16%; + background-color: #eee; +} + +.ni.circle { + border-radius: 50%; +} + +/*------------------------ + list icons +-------------------------*/ +.ni-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} + +.ni-ul>li { + position: relative; +} + +.ni-ul>li>.ni { + position: absolute; + left: -1.57142857em; + top: 0.14285714em; + text-align: center; +} + +.ni-ul>li>.ni.lg { + top: 0; + left: -1.35714286em; +} + +.ni-ul>li>.ni.circle, +.ni-ul>li>.ni.square { + top: -0.19047619em; + left: -1.9047619em; +} + +/*------------------------ + spinning icons +-------------------------*/ +.ni.spin { + -webkit-animation: nc-spin 2s infinite linear; + -moz-animation: nc-spin 2s infinite linear; + animation: nc-spin 2s infinite linear; +} + +@-webkit-keyframes nc-spin { + 0% { + -webkit-transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + } +} + +@-moz-keyframes nc-spin { + 0% { + -moz-transform: rotate(0deg); + } + + 100% { + -moz-transform: rotate(360deg); + } +} + +@keyframes nc-spin { + 0% { + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +/*------------------------ + rotated/flipped icons +-------------------------*/ +.ni.rotate-90 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); +} + +.ni.rotate-180 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); +} + +.ni.rotate-270 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -o-transform: rotate(270deg); + transform: rotate(270deg); +} + +.ni.flip-y { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0); + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); +} + +.ni.flip-x { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: scale(1, -1); + -moz-transform: scale(1, -1); + -ms-transform: scale(1, -1); + -o-transform: scale(1, -1); + transform: scale(1, -1); +} + +/*------------------------ + font icons +-------------------------*/ + +.ni-active-40::before { + content: "\ea02"; +} + +.ni-air-baloon::before { + content: "\ea03"; +} + +.ni-album-2::before { + content: "\ea04"; +} + +.ni-align-center::before { + content: "\ea05"; +} + +.ni-align-left-2::before { + content: "\ea06"; +} + +.ni-ambulance::before { + content: "\ea07"; +} + +.ni-app::before { + content: "\ea08"; +} + +.ni-archive-2::before { + content: "\ea09"; +} + +.ni-atom::before { + content: "\ea0a"; +} + +.ni-badge::before { + content: "\ea0b"; +} + +.ni-bag-17::before { + content: "\ea0c"; +} + +.ni-basket::before { + content: "\ea0d"; +} + +.ni-bell-55::before { + content: "\ea0e"; +} + +.ni-bold-down::before { + content: "\ea0f"; +} + +.ni-bold-left::before { + content: "\ea10"; +} + +.ni-bold-right::before { + content: "\ea11"; +} + +.ni-bold-up::before { + content: "\ea12"; +} + +.ni-bold::before { + content: "\ea13"; +} + +.ni-book-bookmark::before { + content: "\ea14"; +} + +.ni-books::before { + content: "\ea15"; +} + +.ni-box-2::before { + content: "\ea16"; +} + +.ni-briefcase-24::before { + content: "\ea17"; +} + +.ni-building::before { + content: "\ea18"; +} + +.ni-bulb-61::before { + content: "\ea19"; +} + +.ni-bullet-list-67::before { + content: "\ea1a"; +} + +.ni-bus-front-12::before { + content: "\ea1b"; +} + +.ni-button-pause::before { + content: "\ea1c"; +} + +.ni-button-play::before { + content: "\ea1d"; +} + +.ni-button-power::before { + content: "\ea1e"; +} + +.ni-calendar-grid-58::before { + content: "\ea1f"; +} + +.ni-camera-compact::before { + content: "\ea20"; +} + +.ni-caps-small::before { + content: "\ea21"; +} + +.ni-cart::before { + content: "\ea22"; +} + +.ni-chart-bar-32::before { + content: "\ea23"; +} + +.ni-chart-pie-35::before { + content: "\ea24"; +} + +.ni-chat-round::before { + content: "\ea25"; +} + +.ni-check-bold::before { + content: "\ea26"; +} + +.ni-circle-08::before { + content: "\ea27"; +} + +.ni-cloud-download-95::before { + content: "\ea28"; +} + +.ni-cloud-upload-96::before { + content: "\ea29"; +} + +.ni-compass-04::before { + content: "\ea2a"; +} + +.ni-controller::before { + content: "\ea2b"; +} + +.ni-credit-card::before { + content: "\ea2c"; +} + +.ni-curved-next::before { + content: "\ea2d"; +} + +.ni-delivery-fast::before { + content: "\ea2e"; +} + +.ni-diamond::before { + content: "\ea2f"; +} + +.ni-email-83::before { + content: "\ea30"; +} + +.ni-fat-add::before { + content: "\ea31"; +} + +.ni-fat-delete::before { + content: "\ea32"; +} + +.ni-fat-remove::before { + content: "\ea33"; +} + +.ni-favourite-28::before { + content: "\ea34"; +} + +.ni-folder-17::before { + content: "\ea35"; +} + +.ni-glasses-2::before { + content: "\ea36"; +} + +.ni-hat-3::before { + content: "\ea37"; +} + +.ni-headphones::before { + content: "\ea38"; +} + +.ni-html5::before { + content: "\ea39"; +} + +.ni-istanbul::before { + content: "\ea3a"; +} + +.ni-key-25::before { + content: "\ea3b"; +} + +.ni-laptop::before { + content: "\ea3c"; +} + +.ni-like-2::before { + content: "\ea3d"; +} + +.ni-lock-circle-open::before { + content: "\ea3e"; +} + +.ni-map-big::before { + content: "\ea3f"; +} + +.ni-mobile-button::before { + content: "\ea40"; +} + +.ni-money-coins::before { + content: "\ea41"; +} + +.ni-note-03::before { + content: "\ea42"; +} + +.ni-notification-70::before { + content: "\ea43"; +} + +.ni-palette::before { + content: "\ea44"; +} + +.ni-paper-diploma::before { + content: "\ea45"; +} + +.ni-pin-3::before { + content: "\ea46"; +} + +.ni-planet::before { + content: "\ea47"; +} + +.ni-ruler-pencil::before { + content: "\ea48"; +} + +.ni-satisfied::before { + content: "\ea49"; +} + +.ni-scissors::before { + content: "\ea4a"; +} + +.ni-send::before { + content: "\ea4b"; +} + +.ni-settings-gear-65::before { + content: "\ea4c"; +} + +.ni-settings::before { + content: "\ea4d"; +} + +.ni-single-02::before { + content: "\ea4e"; +} + +.ni-single-copy-04::before { + content: "\ea4f"; +} + +.ni-sound-wave::before { + content: "\ea50"; +} + +.ni-spaceship::before { + content: "\ea51"; +} + +.ni-square-pin::before { + content: "\ea52"; +} + +.ni-support-16::before { + content: "\ea53"; +} + +.ni-tablet-button::before { + content: "\ea54"; +} + +.ni-tag::before { + content: "\ea55"; +} + +.ni-tie-bow::before { + content: "\ea56"; +} + +.ni-time-alarm::before { + content: "\ea57"; +} + +.ni-trophy::before { + content: "\ea58"; +} + +.ni-tv-2::before { + content: "\ea59"; +} + +.ni-umbrella-13::before { + content: "\ea5a"; +} + +.ni-user-run::before { + content: "\ea5b"; +} + +.ni-vector::before { + content: "\ea5c"; +} + +.ni-watch-time::before { + content: "\ea5d"; +} + +.ni-world::before { + content: "\ea5e"; +} + +.ni-zoom-split-in::before { + content: "\ea5f"; +} + +.ni-collection::before { + content: "\ea60"; +} + +.ni-image::before { + content: "\ea61"; +} + +.ni-shop::before { + content: "\ea62"; +} + +.ni-ungroup::before { + content: "\ea63"; +} + +.ni-world-2::before { + content: "\ea64"; +} + +.ni-ui-04::before { + content: "\ea65"; +} + + +/* all icon font classes list here */ \ No newline at end of file diff --git a/eveai_app/static/assets/css/nucleo-svg.css b/eveai_app/static/assets/css/nucleo-svg.css new file mode 100644 index 0000000..c68c10e --- /dev/null +++ b/eveai_app/static/assets/css/nucleo-svg.css @@ -0,0 +1,135 @@ +/* Generated using nucleoapp.com */ +/* -------------------------------- + +Icon colors + +-------------------------------- */ + +.icon { + display: inline-block; + /* icon primary color */ + color: #111111; + height: 1em; + width: 1em; +} + +.icon use { + /* icon secondary color - fill */ + fill: #7ea6f6; +} + +.icon.icon-outline use { + /* icon secondary color - stroke */ + stroke: #7ea6f6; +} + +/* -------------------------------- + +Change icon size + +-------------------------------- */ + +.icon-xs { + height: 0.5em; + width: 0.5em; +} + +.icon-sm { + height: 0.8em; + width: 0.8em; +} + +.icon-lg { + height: 1.6em; + width: 1.6em; +} + +.icon-xl { + height: 2em; + width: 2em; +} + +/* -------------------------------- + +Align icon and text + +-------------------------------- */ + +.icon-text-aligner { + /* add this class to parent element that contains icon + text */ + display: flex; + align-items: center; +} + +.icon-text-aligner .icon { + color: inherit; + margin-right: 0.4em; +} + +.icon-text-aligner .icon use { + color: inherit; + fill: currentColor; +} + +.icon-text-aligner .icon.icon-outline use { + stroke: currentColor; +} + +/* -------------------------------- + +Icon reset values - used to enable color customizations + +-------------------------------- */ + +.icon { + fill: currentColor; + stroke: none; +} + +.icon.icon-outline { + fill: none; + stroke: currentColor; +} + +.icon use { + stroke: none; +} + +.icon.icon-outline use { + fill: none; +} + +/* -------------------------------- + +Stroke effects - Nucleo outline icons + +- 16px icons -> up to 1px stroke (16px outline icons do not support stroke changes) +- 24px, 32px icons -> up to 2px stroke +- 48px, 64px icons -> up to 4px stroke + +-------------------------------- */ + +.icon-outline.icon-stroke-1 { + stroke-width: 1px; +} + +.icon-outline.icon-stroke-2 { + stroke-width: 2px; +} + +.icon-outline.icon-stroke-3 { + stroke-width: 3px; +} + +.icon-outline.icon-stroke-4 { + stroke-width: 4px; +} + +.icon-outline.icon-stroke-1 use, +.icon-outline.icon-stroke-3 use { + -webkit-transform: translateX(0.5px) translateY(0.5px); + -moz-transform: translateX(0.5px) translateY(0.5px); + -ms-transform: translateX(0.5px) translateY(0.5px); + -o-transform: translateX(0.5px) translateY(0.5px); + transform: translateX(0.5px) translateY(0.5px); +} \ No newline at end of file diff --git a/eveai_app/templates/eveai_list_view.html b/eveai_app/templates/eveai_list_view.html index 140b455..fa4d48d 100644 --- a/eveai_app/templates/eveai_list_view.html +++ b/eveai_app/templates/eveai_list_view.html @@ -363,50 +363,67 @@ document.addEventListener('DOMContentLoaded', function() { return; } - // Zoek buttons in de volledige formulier context (niet alleen de container) - const form = document.getElementById(`${containerId}-form`) || document.querySelector('form'); - const buttons = form ? form.querySelectorAll('button[onclick*="handleListViewAction"]') : - document.querySelectorAll('button[onclick*="handleListViewAction"]'); + // Zoek buttons zo dicht mogelijk bij de tabel met gegeven containerId + // 1. Probeer eerst een expliciet formulier met id="-form" + let scopeEl = document.getElementById(`${containerId}-form`); + + // 2. Zo niet, gebruik de dichtstbijzijnde .container rond de tabel zelf + if (!scopeEl) { + const tableEl = document.getElementById(containerId); + if (tableEl) { + scopeEl = tableEl.closest('.container') || tableEl.parentElement; + } + } + + // 3. Als er nog steeds geen scope is, val dan terug op de hele document-body + const buttons = (scopeEl || document.body) + .querySelectorAll('button[onclick*="handleListViewAction"]'); console.log(`Updating buttons voor ${containerId}, ${buttons.length} buttons gevonden, selectedRow:`, instance.selectedRow); buttons.forEach(button => { // Parse the onclick attribute to get the action value and requiresSelection parameter - const onclickAttr = button.getAttribute('onclick'); - const match = onclickAttr.match(/handleListViewAction\('([^']+)',\s*(true|false)\)/i); - if (match) { - const actionValue = match[1]; - const requiresSelection = match[2].toLowerCase() === 'true'; + const onclickAttr = button.getAttribute('onclick') || ''; + console.log('ListView _updateActionButtons: inspect button onclickAttr =', onclickAttr); - // Direct toepassen van requiresSelection-check - if (requiresSelection) { - // Controleer of er een geselecteerde rij is - const isDisabled = !instance.selectedRow; - button.disabled = isDisabled; + // Robuustere regex: sta spaties toe en zowel enkele als dubbele quotes + const match = onclickAttr.match(/handleListViewAction\s*\(\s*['"]([^'"\\]+)['"]\s*,\s*(true|false)/i); + if (!match) { + console.warn('ListView _updateActionButtons: geen match gevonden voor handleListViewAction-pattern op deze button'); + return; + } - // Voeg/verwijder disabled class voor styling - if (isDisabled) { - button.classList.add('disabled'); - } else { - button.classList.remove('disabled'); - } + const actionValue = match[1]; + const requiresSelection = match[2].toLowerCase() === 'true'; - console.log(`Button ${actionValue} updated: disabled=${isDisabled}`); + // Direct toepassen van requiresSelection-check + if (requiresSelection) { + // Controleer of er een geselecteerde rij is + const isDisabled = !instance.selectedRow; + button.disabled = isDisabled; + + // Voeg/verwijder disabled class voor styling + if (isDisabled) { + button.classList.add('disabled'); + } else { + button.classList.remove('disabled'); } - // Backup check op basis van actions in config (voor achterwaartse compatibiliteit) - const action = instance.config.actions.find(a => a.value === actionValue); - if (action && action.requiresSelection === true && !requiresSelection) { - // Ook controleren op basis van action config - const isDisabled = !instance.selectedRow; - button.disabled = isDisabled; + console.log(`Button ${actionValue} updated: disabled=${isDisabled}`); + } - // Voeg/verwijder disabled class voor styling - if (isDisabled) { - button.classList.add('disabled'); - } else { - button.classList.remove('disabled'); - } + // Backup check op basis van actions in config (voor achterwaartse compatibiliteit) + const action = instance.config.actions.find(a => a.value === actionValue); + if (action && action.requiresSelection === true && !requiresSelection) { + // Ook controleren op basis van action config + const isDisabled = !instance.selectedRow; + button.disabled = isDisabled; + + // Voeg/verwijder disabled class voor styling + if (isDisabled) { + button.classList.add('disabled'); + } else { + button.classList.remove('disabled'); } } }); diff --git a/eveai_app/templates/interaction/edit_specialist.html b/eveai_app/templates/interaction/edit_specialist.html index 4e23a57..2275ad4 100644 --- a/eveai_app/templates/interaction/edit_specialist.html +++ b/eveai_app/templates/interaction/edit_specialist.html @@ -4,7 +4,7 @@ {% block title %}Edit Specialist{% endblock %} {% block content_title %}Edit Specialist{% endblock %} -{% block content_description %}Edit a Specialist and its components{% endblock %} +{% block content_description %}Edit a Specialist{% endblock %} {% block content %}
@@ -31,11 +31,6 @@ Configuration -
@@ -48,17 +43,17 @@ {% endfor %} -{#
#} -{#
#} -{#
#} -{#
#} -{#
#} -{# Specialist Overview#} -{#
#} -{#
#} -{#
#} -{#
#} -{#
#} + {#
#} + {#
#} + {#
#} + {#
#} + {#
#} + {# Specialist Overview#} + {#
#} + {#
#} + {#
#} + {#
#} + {#
#} @@ -72,29 +67,6 @@ {% endfor %} {% endfor %} - - -
-
-
-
- - -
-
-
- -
-
-
-
-
-
@@ -105,299 +77,8 @@ - - {% endblock %} {% block scripts %} {{ super() }} - - - - {% endblock %} diff --git a/eveai_app/templates/interaction/tune_specialist.html b/eveai_app/templates/interaction/tune_specialist.html new file mode 100644 index 0000000..817548a --- /dev/null +++ b/eveai_app/templates/interaction/tune_specialist.html @@ -0,0 +1,257 @@ +{% 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 {{ specialist.name }} (ID: {{ specialist.id }})
+ ({{ specialist.type }} - {{ specialist.type_version }}){% endblock %} + +{% block content %} +
+
+
+ + +
+
+
Components
+ {# 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 %} +
+
+
+
+
+ + + +{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} diff --git a/eveai_app/views/interaction_views.py b/eveai_app/views/interaction_views.py index 312233b..88b41f5 100644 --- a/eveai_app/views/interaction_views.py +++ b/eveai_app/views/interaction_views.py @@ -282,26 +282,44 @@ def edit_specialist(specialist_id): else: form_validation_failed(request, form) - # Build combined components list view config for embedding - from eveai_app.views.list_views.interaction_list_views import get_specialist_components_list_view - components_config = get_specialist_components_list_view(specialist) - return render_template('interaction/edit_specialist.html', form=form, specialist_id=specialist_id, - components_title=components_config.get('title'), - components_data=components_config.get('data'), - components_columns=components_config.get('columns'), - components_actions=components_config.get('actions'), - components_initial_sort=components_config.get('initial_sort'), - components_table_id=components_config.get('table_id'), - components_table_height=components_config.get('table_height'), - components_description=components_config.get('description'), - components_index=components_config.get('index'), prefixed_url_for=prefixed_url_for, svg_path=svg_path, ) +@interaction_bp.route('/tune_specialist/', methods=['GET']) +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def tune_specialist(specialist_id): + """Standalone tuning view voor het beheren van agents, tasks en tools van een specialist. + + Deze view biedt dezelfde component-editor functionaliteit die voorheen in de + Components-tab van ``edit_specialist`` zat, maar dan als aparte pagina. + """ + specialist = Specialist.query.get_or_404(specialist_id) + + # Gebruikt dezelfde gecombineerde componenten-configuratie als de voormalige tab + from eveai_app.views.list_views.interaction_list_views import get_specialist_components_list_view + components_config = get_specialist_components_list_view(specialist) + + return render_template( + 'interaction/tune_specialist.html', + specialist=specialist, + specialist_id=specialist_id, + components_title=components_config.get('title'), + components_data=components_config.get('data'), + components_columns=components_config.get('columns'), + components_actions=components_config.get('actions'), + components_initial_sort=components_config.get('initial_sort'), + components_table_id=components_config.get('table_id'), + components_table_height=components_config.get('table_height'), + components_description=components_config.get('description'), + components_index=components_config.get('index'), + prefixed_url_for=prefixed_url_for, + ) + + @interaction_bp.route('/specialists', methods=['GET', 'POST']) @roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') def specialists(): @@ -323,6 +341,8 @@ def handle_specialist_selection(): if action == "edit_specialist": return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id, for_redirect=True)) + elif action == "tune_specialist": + return redirect(prefixed_url_for('interaction_bp.tune_specialist', specialist_id=specialist_id, for_redirect=True)) elif action == "execute_specialist": return redirect(prefixed_url_for('interaction_bp.execute_specialist', specialist_id=specialist_id, for_redirect=True)) diff --git a/eveai_app/views/list_views/interaction_list_views.py b/eveai_app/views/list_views/interaction_list_views.py index 2956659..b161f07 100644 --- a/eveai_app/views/list_views/interaction_list_views.py +++ b/eveai_app/views/list_views/interaction_list_views.py @@ -38,6 +38,7 @@ def get_specialists_list_view(): # Action definitions actions = [ {'value': 'edit_specialist', 'text': 'Edit Specialist', 'class': 'btn-primary', 'requiresSelection': True}, + {'value': 'tune_specialist', 'text': 'Tune Specialist', 'class': 'btn-secondary', 'requiresSelection': True}, {'value': 'execute_specialist', 'text': 'Execute Specialist', 'class': 'btn-primary', 'requiresSelection': True}, {'value': 'create_specialist', 'text': 'Register Specialist', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False} ]