- Specialist Editor Change (all components in same overview), modal editors to allow for more complex configuration of Agents, Tasks and Tools
- Strengthening dynamic forms
This commit is contained in:
@@ -97,7 +97,8 @@ class OrderedListField(TextAreaField):
|
||||
|
||||
class DynamicFormBase(FlaskForm):
|
||||
def __init__(self, formdata=None, *args, **kwargs):
|
||||
super(DynamicFormBase, self).__init__(*args, **kwargs)
|
||||
# Belangrijk: formdata doorgeven aan FlaskForm zodat WTForms POST-data kan binden
|
||||
super(DynamicFormBase, self).__init__(formdata=formdata, *args, **kwargs)
|
||||
# Maps collection names to lists of field names
|
||||
self.dynamic_fields = {}
|
||||
# Store formdata for later use
|
||||
|
||||
@@ -98,10 +98,17 @@ class EditEveAIAgentForm(BaseEditComponentForm):
|
||||
obj = kwargs.get('obj')
|
||||
agent_type = None
|
||||
agent_type_version = None
|
||||
current_llm_model = None
|
||||
current_temperature = None
|
||||
if obj:
|
||||
agent_type = obj.type
|
||||
agent_type_version = obj.type_version
|
||||
current_llm_model = obj.llm_model
|
||||
current_temperature = obj.temperature
|
||||
|
||||
# Bewaar flags over oorspronkelijke None-status voor optionele normalisatie in populate_obj
|
||||
self._was_llm_model_none = (current_llm_model is None)
|
||||
self._was_temperature_none = (current_temperature is None)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -111,6 +118,8 @@ class EditEveAIAgentForm(BaseEditComponentForm):
|
||||
self._agent_config = cache_manager.agents_config_cache.get_config(agent_type, agent_type_version)
|
||||
allowed_models = self._agent_config.get('allowed_models', None)
|
||||
full_model_name = self._agent_config.get('full_model_name', 'mistral.mistral-medium-latest')
|
||||
default_temperature = self._agent_config.get('temperature', 0.7)
|
||||
|
||||
if allowed_models:
|
||||
# Converteer lijst van strings naar lijst van tuples (value, label)
|
||||
self.llm_model.choices = [(model, model) for model in allowed_models]
|
||||
@@ -124,26 +133,36 @@ class EditEveAIAgentForm(BaseEditComponentForm):
|
||||
# Gebruik full_model_name als fallback
|
||||
self.llm_model.choices = [(full_model_name, full_model_name)]
|
||||
|
||||
# Als er GEEN waarde in de database staat, toon dan de default uit de config
|
||||
if not current_llm_model:
|
||||
self.llm_model.data = full_model_name
|
||||
# Defaults alleen instellen wanneer er geen formdata is (GET render of programmatic constructie)
|
||||
is_post = bool(getattr(self, 'formdata', None))
|
||||
if not is_post:
|
||||
if current_llm_model is None:
|
||||
self.llm_model.data = full_model_name
|
||||
if current_temperature is None:
|
||||
self.temperature.data = default_temperature
|
||||
else:
|
||||
self.llm_model.choices = [('mistral.mistral-medium-latest', 'mistral.mistral-medium-latest')]
|
||||
|
||||
def populate_obj(self, obj):
|
||||
"""Override populate_obj om de None waarde te behouden indien nodig"""
|
||||
original_llm_model = obj.llm_model
|
||||
|
||||
# Roep de parent populate_obj aan
|
||||
current_app.logger.info(f"populate_obj called with obj: {obj}")
|
||||
super().populate_obj(obj)
|
||||
current_app.logger.info(f"populate_obj done with obj: {obj}")
|
||||
|
||||
# Als de originele waarde None was EN de nieuwe waarde gelijk is aan de config default,
|
||||
# herstel dan de None waarde
|
||||
if original_llm_model is None and self._agent_config:
|
||||
# herstel dan de None waarde (alleen als het eerder None was)
|
||||
if getattr(self, '_agent_config', None):
|
||||
full_model_name = self._agent_config.get('full_model_name', 'mistral.mistral-medium-latest')
|
||||
if obj.llm_model == full_model_name:
|
||||
if self._was_llm_model_none and obj.llm_model == full_model_name:
|
||||
obj.llm_model = None
|
||||
|
||||
default_temperature = self._agent_config.get('temperature', 0.7)
|
||||
if self._was_temperature_none and obj.temperature == default_temperature:
|
||||
obj.temperature = None
|
||||
|
||||
current_app.logger.info(f"populate_obj default check results in obj: {obj}")
|
||||
|
||||
|
||||
class EditEveAITaskForm(BaseEditComponentForm):
|
||||
task_description = StringField('Task Description', validators=[Optional()])
|
||||
|
||||
@@ -262,23 +262,42 @@ def edit_specialist(specialist_id):
|
||||
db.session.rollback()
|
||||
flash(f'Failed to update specialist. Error: {str(e)}', 'danger')
|
||||
current_app.logger.error(f'Failed to update specialist {specialist_id}. Error: {str(e)}')
|
||||
# On error, re-render with components list config
|
||||
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,
|
||||
agent_rows=agent_rows,
|
||||
task_rows=task_rows,
|
||||
tool_rows=tool_rows,
|
||||
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, )
|
||||
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,
|
||||
agent_rows=agent_rows,
|
||||
task_rows=task_rows,
|
||||
tool_rows=tool_rows,
|
||||
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, )
|
||||
|
||||
@@ -310,6 +329,15 @@ def handle_specialist_selection():
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True))
|
||||
|
||||
|
||||
@interaction_bp.route('/specialist/<int:specialist_id>/components_data', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def specialist_components_data(specialist_id):
|
||||
"""Return JSON data for the specialist's combined components list (agents, tasks, tools)."""
|
||||
specialist = Specialist.query.get_or_404(specialist_id)
|
||||
from eveai_app.views.list_views.interaction_list_views import get_specialist_components_list_view
|
||||
config = get_specialist_components_list_view(specialist)
|
||||
return jsonify({'data': config.get('data', [])})
|
||||
|
||||
# Routes for Agent management ---------------------------------------------------------------------
|
||||
@interaction_bp.route('/agent/<int:agent_id>/edit', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
@@ -318,22 +346,34 @@ def edit_agent(agent_id):
|
||||
form = EditEveAIAgentForm(obj=agent)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
# Determine defaults for reset button if available
|
||||
enable_reset_defaults = False
|
||||
model_default = ''
|
||||
temperature_default = ''
|
||||
if getattr(form, '_agent_config', None):
|
||||
model_default = form._agent_config.get('full_model_name', 'mistral.mistral-medium-latest')
|
||||
temperature_default = form._agent_config.get('temperature', 0.7)
|
||||
enable_reset_defaults = True
|
||||
# Return just the form portion for AJAX requests
|
||||
return render_template('interaction/components/edit_agent.html',
|
||||
form=form,
|
||||
agent=agent,
|
||||
title="Edit Agent",
|
||||
description="Configure the agent with company-specific details if required",
|
||||
submit_text="Save Agent")
|
||||
submit_text="Save Agent",
|
||||
enable_reset_defaults=enable_reset_defaults,
|
||||
model_default=model_default,
|
||||
temperature_default=temperature_default)
|
||||
return None
|
||||
|
||||
|
||||
@interaction_bp.route('/agent/<int:agent_id>/save', methods=['POST'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def save_agent(agent_id):
|
||||
current_app.logger.info(f'Trying to save agent {agent_id} -------------------------------------------')
|
||||
agent = EveAIAgent.query.get_or_404(agent_id) if agent_id else EveAIAgent()
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
form = EditEveAIAgentForm(obj=agent)
|
||||
form = EditEveAIAgentForm(formdata=request.form, obj=agent)
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
@@ -347,8 +387,25 @@ def save_agent(agent_id):
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f'Failed to save agent {agent_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||
return jsonify({'success': False, 'message': f"Failed to save agent {agent_id}: {str(e)}"})
|
||||
|
||||
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||
else:
|
||||
# On validation errors, return the editor partial HTML so the frontend can display inline errors in the modal
|
||||
form_validation_failed(request, form)
|
||||
enable_reset_defaults = False
|
||||
model_default = ''
|
||||
temperature_default = ''
|
||||
if getattr(form, '_agent_config', None):
|
||||
model_default = form._agent_config.get('full_model_name', 'mistral.mistral-medium-latest')
|
||||
temperature_default = form._agent_config.get('temperature', 0.7)
|
||||
enable_reset_defaults = True
|
||||
return render_template('interaction/components/edit_agent.html',
|
||||
form=form,
|
||||
agent=agent,
|
||||
title="Edit Agent",
|
||||
description="Configure the agent with company-specific details if required",
|
||||
submit_text="Save Agent",
|
||||
enable_reset_defaults=enable_reset_defaults,
|
||||
model_default=model_default,
|
||||
temperature_default=temperature_default), 400
|
||||
|
||||
|
||||
# Routes for Task management ----------------------------------------------------------------------
|
||||
@@ -374,7 +431,7 @@ def edit_task(task_id):
|
||||
def save_task(task_id):
|
||||
task = EveAITask.query.get_or_404(task_id) if task_id else EveAITask()
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
form = EditEveAITaskForm(obj=task) # Replace with actual task form
|
||||
form = EditEveAITaskForm(formdata=request.form, obj=task) # Bind explicit formdata
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
@@ -389,7 +446,14 @@ def save_task(task_id):
|
||||
current_app.logger.error(f'Failed to save task {task_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||
return jsonify({'success': False, 'message': f"Failed to save task {task_id}: {str(e)}"})
|
||||
|
||||
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||
# On validation errors, return the editor partial HTML (400) so frontend can show inline errors
|
||||
form_validation_failed(request, form)
|
||||
return render_template('interaction/components/edit_task.html',
|
||||
form=form,
|
||||
task=task,
|
||||
title="Edit Task",
|
||||
description="Configure the task with company-specific details if required",
|
||||
submit_text="Save Task"), 400
|
||||
|
||||
|
||||
# Routes for Tool management ----------------------------------------------------------------------
|
||||
@@ -415,7 +479,7 @@ def edit_tool(tool_id):
|
||||
def save_tool(tool_id):
|
||||
tool = EveAITool.query.get_or_404(tool_id) if tool_id else EveAITool()
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
form = EditEveAIToolForm(obj=tool) # Replace with actual tool form
|
||||
form = EditEveAIToolForm(formdata=request.form, obj=tool)
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
@@ -430,7 +494,14 @@ def save_tool(tool_id):
|
||||
current_app.logger.error(f'Failed to save tool {tool_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||
return jsonify({'success': False, 'message': f"Failed to save tool {tool_id}: {str(e)}"})
|
||||
|
||||
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||
# On validation errors, return the editor partial HTML (400)
|
||||
form_validation_failed(request, form)
|
||||
return render_template('interaction/components/edit_tool.html',
|
||||
form=form,
|
||||
tool=tool,
|
||||
title="Edit Tool",
|
||||
description="Configure the tool with company-specific details if required",
|
||||
submit_text="Save Tool"), 400
|
||||
|
||||
|
||||
# Component selection handlers --------------------------------------------------------------------
|
||||
|
||||
@@ -245,3 +245,74 @@ def get_eveai_data_capsules_list_view():
|
||||
'table_height': 800
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Combined specialist components list view helper
|
||||
def get_specialist_components_list_view(specialist):
|
||||
"""Generate a combined list view configuration for a specialist's agents, tasks, and tools"""
|
||||
# Build unified data rows: id, name, type_name (agent|task|tool), type, type_version
|
||||
data = []
|
||||
|
||||
# Agents
|
||||
for agent in getattr(specialist, 'agents', []) or []:
|
||||
data.append({
|
||||
'id': agent.id,
|
||||
'name': getattr(agent, 'name', f'Agent {agent.id}'),
|
||||
'type_name': 'agent',
|
||||
'type': agent.type,
|
||||
'type_version': agent.type_version,
|
||||
'row_key': f"agent:{agent.id}",
|
||||
})
|
||||
|
||||
# Tasks
|
||||
for task in getattr(specialist, 'tasks', []) or []:
|
||||
data.append({
|
||||
'id': task.id,
|
||||
'name': getattr(task, 'name', f'Task {task.id}'),
|
||||
'type_name': 'task',
|
||||
'type': task.type,
|
||||
'type_version': task.type_version,
|
||||
'row_key': f"task:{task.id}",
|
||||
})
|
||||
|
||||
# Tools
|
||||
for tool in getattr(specialist, 'tools', []) or []:
|
||||
data.append({
|
||||
'id': tool.id,
|
||||
'name': getattr(tool, 'name', f'Tool {tool.id}'),
|
||||
'type_name': 'tool',
|
||||
'type': tool.type,
|
||||
'type_version': tool.type_version,
|
||||
'row_key': f"tool:{tool.id}",
|
||||
})
|
||||
|
||||
current_app.logger.debug(f'Combined specialist components list view data: \n{data}')
|
||||
|
||||
# Sort ascending by id as requested
|
||||
data.sort(key=lambda r: (r.get('id') or 0))
|
||||
|
||||
columns = [
|
||||
{'title': 'ID', 'field': 'id', 'width': 80},
|
||||
{'title': 'Name', 'field': 'name'},
|
||||
{'title': 'Kind', 'field': 'type_name', 'formatter': 'typeBadge'},
|
||||
{'title': 'Type', 'field': 'type'},
|
||||
{'title': 'Type Version', 'field': 'type_version'},
|
||||
]
|
||||
|
||||
actions = [
|
||||
{'value': 'edit_component', 'text': 'Edit', 'class': 'btn-primary', 'requiresSelection': True},
|
||||
]
|
||||
|
||||
initial_sort = [{'column': 'id', 'dir': 'asc'}]
|
||||
|
||||
return {
|
||||
'title': 'Components',
|
||||
'data': data,
|
||||
'columns': columns,
|
||||
'actions': actions,
|
||||
'initial_sort': initial_sort,
|
||||
'table_id': 'specialist_components_table',
|
||||
'description': 'Agents, Tasks, and Tools associated with this specialist',
|
||||
'table_height': 600,
|
||||
'index': 'row_key',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user