- Add a new 'system' type to dynamic forms, first one defined = 'tenant_make'

- Add active field to Specialist model
- Improve Specialists view
- Propagate make for Role Definition Specialist to Selection Specialist (make is defined at the role level)
- Ensure a make with a given name can only be defined once
This commit is contained in:
Josako
2025-06-09 11:06:36 +02:00
parent 43ee9139d6
commit c4dcd6a0d3
15 changed files with 679 additions and 13 deletions

View File

@@ -10,7 +10,7 @@
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('interaction_bp.handle_specialist_selection') }}" id="specialistsForm">
{{ render_selectable_table(headers=["Specialist ID", "Name", "Type"], rows=rows, selectable=True, id="specialistsTable") }}
{{ render_selectable_table(headers=["Specialist ID", "Name", "Type", "Type Version", "Active"], rows=rows, selectable=True, id="specialistsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_specialist" class="btn btn-primary" onclick="return validateTableSelection('specialistsForm')">Edit Specialist</button>

View File

@@ -3,12 +3,14 @@ from datetime import date
from flask_wtf import FlaskForm
from wtforms import (IntegerField, FloatField, BooleanField, StringField, TextAreaField, FileField,
validators, ValidationError)
from flask import current_app, request
from flask import current_app, request, session
import json
from wtforms.fields.choices import SelectField
from wtforms.fields.datetime import DateField
from wtforms.fields.simple import ColorField
from common.models.user import TenantMake
from common.utils.config_field_types import TaggingFields, json_to_patterns, patterns_to_json
@@ -300,6 +302,22 @@ class DynamicFormBase(FlaskForm):
except Exception as e:
raise ValidationError(f"Invalid ordered list: {str(e)}")
def _get_system_field(self, system_name):
"""Get the field class and kwargs for a system field. Add system field cases as you need them."""
field_class = None
extra_classes = ''
field_kwargs = {}
match system_name:
case 'tenant_make':
field_class = SelectField
tenant_id = session.get('tenant').get('id')
makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
choices = [(make.name, make.name) for make in makes]
extra_classes = ''
field_kwargs = {'choices': choices}
return field_class, extra_classes, field_kwargs
def add_dynamic_fields(self, collection_name, config, initial_data=None):
"""Add dynamic fields to the form based on the configuration.
@@ -357,11 +375,12 @@ class DynamicFormBase(FlaskForm):
extra_classes = ['monospace-text', 'pattern-input']
field_kwargs = {}
elif field_type == 'ordered_list':
current_app.logger.debug(f"Adding ordered list field for {full_field_name}")
field_class = OrderedListField
extra_classes = ''
list_type = field_def.get('list_type', '')
field_kwargs = {'list_type': list_type}
elif field_type == 'system':
field_class, extra_classes, field_kwargs = self._get_system_field(field_def.get('system_name', ''))
else:
extra_classes = ''
field_class = {

View File

@@ -24,6 +24,7 @@ def get_tools():
class SpecialistForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
description = TextAreaField('Description', validators=[Optional()])
retrievers = QuerySelectMultipleField(
'Retrievers',
@@ -34,7 +35,7 @@ class SpecialistForm(FlaskForm):
)
type = SelectField('Specialist Type', validators=[DataRequired()])
active = BooleanField('Active', validators=[Optional()], default=True)
tuning = BooleanField('Enable Specialist Tuning', default=False)
def __init__(self, *args, **kwargs):
@@ -47,6 +48,7 @@ class SpecialistForm(FlaskForm):
class EditSpecialistForm(DynamicFormBase):
name = StringField('Name', validators=[DataRequired()])
description = TextAreaField('Description', validators=[Optional()])
active = BooleanField('Active', validators=[Optional()], default=True)
retrievers = QuerySelectMultipleField(
'Retrievers',

View File

@@ -162,6 +162,7 @@ def specialist():
new_specialist.type = form.type.data
new_specialist.type_version = cache_manager.specialists_version_tree_cache.get_latest_version(
new_specialist.type)
new_specialist.active = form.active.data
new_specialist.tuning = form.tuning.data
set_logging_information(new_specialist, dt.now(tz.utc))
@@ -231,6 +232,7 @@ def edit_specialist(specialist_id):
specialist.name = form.name.data
specialist.description = form.description.data
specialist.tuning = form.tuning.data
specialist.active = form.active.data
# Update the configuration dynamic fields
specialist.configuration = form.get_dynamic_data("configuration")
@@ -297,7 +299,7 @@ def specialists():
# prepare table data
rows = prepare_table_for_macro(the_specialists,
[('id', ''), ('name', ''), ('type', '')])
[('id', ''), ('name', ''), ('type', ''), ('type_version', ''), ('active', ''),])
# Render the catalogs in a template
return render_template('interaction/specialists.html', rows=rows, pagination=pagination)

View File

@@ -2,10 +2,12 @@ from flask import current_app, session
from flask_wtf import FlaskForm
from wtforms import (StringField, BooleanField, SubmitField, EmailField, IntegerField, DateField,
SelectField, SelectMultipleField, FieldList, FormField, TextAreaField)
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, ValidationError
import pytz
from flask_security import current_user
from wtforms.widgets.core import HiddenInput
from common.models.user import TenantMake
from common.services.user import UserServices
from config.type_defs.service_types import SERVICE_TYPES
from eveai_app.views.dynamic_form_base import DynamicFormBase
@@ -132,8 +134,19 @@ class EditTenantProjectForm(FlaskForm):
self.services.choices = [(key, value['description']) for key, value in SERVICE_TYPES.items()]
def validate_make_name(form, field):
# Controleer of een TenantMake met deze naam al bestaat
existing_make = TenantMake.query.filter_by(name=field.data).first()
# Als er een bestaande make is gevonden en we zijn niet in edit mode,
# of als we wel in edit mode zijn maar het is een ander record (andere id)
if existing_make and (not hasattr(form, 'id') or form.id.data != existing_make.id):
raise ValidationError(f'A Make with name "{field.data}" already exists. Choose another name.')
class TenantMakeForm(DynamicFormBase):
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
id = IntegerField('ID', widget=HiddenInput())
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
description = TextAreaField('Description', validators=[Optional()])
active = BooleanField('Active', validators=[Optional()], default=True)
website = StringField('Website', validators=[DataRequired(), Length(max=255)])

View File

@@ -1,3 +1,4 @@
import json
import uuid
from datetime import datetime as dt, timezone as tz
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
@@ -627,15 +628,17 @@ def delete_tenant_project(tenant_project_id):
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def tenant_make():
form = TenantMakeForm()
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
default_customisation_options = create_default_config_from_type_config(customisation_config["configuration"])
form.add_dynamic_fields("configuration", customisation_config, default_customisation_options)
if form.validate_on_submit():
tenant_id = session['tenant']['id']
new_tenant_make = TenantMake()
form.populate_obj(new_tenant_make)
new_tenant_make.tenant_id = tenant_id
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
new_tenant_make.chat_customisation_options = create_default_config_from_type_config(
customisation_config["configuration"])
form.add_dynamic_fields("configuration", customisation_config, new_tenant_make.chat_customisation_options)
customisation_options = form.get_dynamic_data("configuration")
new_tenant_make.chat_customisation_options = json.dumps(customisation_options)
set_logging_information(new_tenant_make, dt.now(tz.utc))
try: