- Finalisation of the Specialist model, forms and views
This commit is contained in:
@@ -8,7 +8,6 @@ import json
|
||||
|
||||
from wtforms_sqlalchemy.fields import QuerySelectField
|
||||
|
||||
from common.extensions import db
|
||||
from common.models.document import Catalog
|
||||
|
||||
from config.catalog_types import CATALOG_TYPES
|
||||
@@ -42,7 +41,6 @@ class CatalogForm(FlaskForm):
|
||||
# Metadata fields
|
||||
user_metadata = TextAreaField('User Metadata', validators=[Optional(), validate_json])
|
||||
system_metadata = TextAreaField('System Metadata', validators=[Optional(), validate_json])
|
||||
configuration = TextAreaField('Configuration', validators=[Optional(), validate_json])
|
||||
|
||||
# HTML Embedding Variables
|
||||
html_tags = StringField('HTML Tags', validators=[DataRequired()],
|
||||
|
||||
@@ -228,8 +228,6 @@ def edit_retriever(retriever_id):
|
||||
|
||||
configuration_config = RETRIEVER_TYPES[retriever.type]["configuration"]
|
||||
form.add_dynamic_fields("configuration", configuration_config, retriever.configuration)
|
||||
if request.method == 'POST':
|
||||
current_app.logger.debug(f'Received POST request with {request.form}')
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update basic fields
|
||||
@@ -258,7 +256,6 @@ def edit_retriever(retriever_id):
|
||||
else:
|
||||
form_validation_failed(request, form)
|
||||
|
||||
current_app.logger.debug(f"Rendering Template for {retriever_id}")
|
||||
return render_template('document/edit_retriever.html', form=form, retriever_id=retriever_id)
|
||||
|
||||
|
||||
|
||||
60
eveai_app/views/interaction_forms.py
Normal file
60
eveai_app/views/interaction_forms.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from flask import session, current_app, request
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (StringField, BooleanField, SubmitField, DateField, IntegerField, FloatField, SelectMultipleField,
|
||||
SelectField, FieldList, FormField, TextAreaField, URLField)
|
||||
from wtforms.validators import DataRequired, Length, Optional, URL, ValidationError, NumberRange
|
||||
from flask_wtf.file import FileField, FileAllowed, FileRequired
|
||||
import json
|
||||
|
||||
from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
|
||||
|
||||
from common.models.document import Retriever
|
||||
|
||||
from config.catalog_types import CATALOG_TYPES
|
||||
from config.specialist_types import SPECIALIST_TYPES
|
||||
from .dynamic_form_base import DynamicFormBase
|
||||
|
||||
|
||||
def get_retrievers():
|
||||
return Retriever.query.all()
|
||||
|
||||
|
||||
class SpecialistForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[DataRequired()])
|
||||
|
||||
retrievers = QuerySelectMultipleField(
|
||||
'Retrievers',
|
||||
query_factory=get_retrievers,
|
||||
get_label='name', # Assuming your Retriever model has a 'name' field
|
||||
allow_blank=True,
|
||||
description='Select one or more retrievers to associate with this specialist'
|
||||
)
|
||||
|
||||
type = SelectField('Specialist Type', validators=[DataRequired()])
|
||||
|
||||
tuning = BooleanField('Enable Retrieval Tuning', default=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Dynamically populate the 'type' field using the constructor
|
||||
self.type.choices = [(key, value['name']) for key, value in SPECIALIST_TYPES.items()]
|
||||
|
||||
|
||||
class EditSpecialistForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired()])
|
||||
description = TextAreaField('Description', validators=[DataRequired()])
|
||||
|
||||
retrievers = QuerySelectMultipleField(
|
||||
'Retrievers',
|
||||
query_factory=get_retrievers,
|
||||
get_label='name',
|
||||
allow_blank=True,
|
||||
description='Select one or more retrievers to associate with this specialist'
|
||||
)
|
||||
|
||||
type = StringField('Specialist Type', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
tuning = BooleanField('Enable Retrieval Tuning', default=False)
|
||||
|
||||
|
||||
|
||||
@@ -15,14 +15,17 @@ from requests.exceptions import SSLError
|
||||
from urllib.parse import urlparse
|
||||
import io
|
||||
|
||||
from common.models.document import Embedding, DocumentVersion
|
||||
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding
|
||||
from common.models.document import Embedding, DocumentVersion, Retriever
|
||||
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever
|
||||
from common.extensions import db
|
||||
from common.utils.document_utils import set_logging_information, update_logging_information
|
||||
from config.specialist_types import SPECIALIST_TYPES
|
||||
from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm
|
||||
from common.utils.middleware import mw_before_request
|
||||
from common.utils.celery_utils import current_celery
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro
|
||||
from .interaction_forms import SpecialistForm, EditSpecialistForm
|
||||
|
||||
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
|
||||
|
||||
@@ -122,3 +125,150 @@ def show_chat_session(chat_session):
|
||||
interactions = Interaction.query.filter_by(chat_session_id=chat_session.id).all()
|
||||
return render_template('interaction/view_chat_session.html', chat_session=chat_session, interactions=interactions)
|
||||
|
||||
|
||||
@interaction_bp.route('/specialist', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def specialist():
|
||||
form = SpecialistForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
try:
|
||||
new_specialist = Specialist()
|
||||
|
||||
# Populate fields individually instead of using populate_obj (gives problem with QueryMultipleSelectField)
|
||||
new_specialist.name = form.name.data
|
||||
new_specialist.description = form.description.data
|
||||
new_specialist.type = form.type.data
|
||||
new_specialist.tuning = form.tuning.data
|
||||
|
||||
set_logging_information(new_specialist, dt.now(tz.utc))
|
||||
|
||||
db.session.add(new_specialist)
|
||||
db.session.flush() # This assigns the ID to the specialist without committing the transaction
|
||||
|
||||
current_app.logger.debug(
|
||||
f'New Specialist after flush - id: {new_specialist.id}, name: {new_specialist.name}')
|
||||
|
||||
# Create the retriever associations
|
||||
selected_retrievers = form.retrievers.data
|
||||
current_app.logger.debug(f'Selected Retrievers - {selected_retrievers}')
|
||||
for retriever in selected_retrievers:
|
||||
current_app.logger.debug(f'Creating association for Retriever - {retriever.id}')
|
||||
specialist_retriever = SpecialistRetriever(
|
||||
specialist_id=new_specialist.id,
|
||||
retriever_id=retriever.id
|
||||
)
|
||||
db.session.add(specialist_retriever)
|
||||
|
||||
# Commit everything in one transaction
|
||||
db.session.commit()
|
||||
flash('Specialist successfully added!', 'success')
|
||||
current_app.logger.info(f'Specialist {new_specialist.name} successfully added for tenant {tenant_id}!')
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id))
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f'Failed to add specialist. Error: {str(e)}', exc_info=True)
|
||||
flash(f'Failed to add specialist. Error: {str(e)}', 'danger')
|
||||
return render_template('interaction/specialist.html', form=form)
|
||||
|
||||
return render_template('interaction/specialists.html', form=form)
|
||||
|
||||
|
||||
@interaction_bp.route('/specialist/<int:specialist_id>', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def edit_specialist(specialist_id):
|
||||
specialist = Specialist.query.get_or_404(specialist_id)
|
||||
form = EditSpecialistForm(request.form, obj=specialist)
|
||||
|
||||
configuration_config = SPECIALIST_TYPES[specialist.type]["configuration"]
|
||||
form.add_dynamic_fields("configuration", configuration_config, specialist.configuration)
|
||||
|
||||
if request.method == 'GET':
|
||||
# Pre-populate the retrievers field with current associations
|
||||
current_app.logger.debug(f'Specialist retrievers: {specialist.retrievers}')
|
||||
current_app.logger.debug(f'Form Retrievers Data Before: {form.retrievers.data}')
|
||||
|
||||
# Get the actual Retriever objects for the associated retriever_ids
|
||||
retriever_objects = Retriever.query.filter(
|
||||
Retriever.id.in_([sr.retriever_id for sr in specialist.retrievers])
|
||||
).all()
|
||||
form.retrievers.data = retriever_objects
|
||||
|
||||
current_app.logger.debug(f'Form Retrievers Data After: {form.retrievers.data}')
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update the basic fields
|
||||
form.populate_obj(specialist)
|
||||
# Update the configuration dynamic fields
|
||||
specialist.configuration = form.get_dynamic_data("configuration")
|
||||
|
||||
# Update retriever associations
|
||||
current_retrievers = set(sr.retriever_id for sr in specialist.retrievers)
|
||||
selected_retrievers = set(r.id for r in form.retrievers.data)
|
||||
|
||||
# Remove unselected retrievers
|
||||
for sr in specialist.retrievers[:]:
|
||||
if sr.retriever_id not in selected_retrievers:
|
||||
db.session.delete(sr)
|
||||
|
||||
# Add new retrievers
|
||||
for retriever_id in selected_retrievers - current_retrievers:
|
||||
specialist_retriever = SpecialistRetriever(
|
||||
specialist_id=specialist.id,
|
||||
retriever_id=retriever_id
|
||||
)
|
||||
db.session.add(specialist_retriever)
|
||||
|
||||
# Update logging information
|
||||
update_logging_information(specialist, dt.now(tz.utc))
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
flash('Specialist updated successfully!', 'success')
|
||||
current_app.logger.info(f'Specialist {specialist.id} updated successfully')
|
||||
except SQLAlchemyError as e:
|
||||
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)}')
|
||||
return render_template('interaction/edit_specialist.html', form=form, specialist_id=specialist_id)
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
else:
|
||||
form_validation_failed(request, form)
|
||||
|
||||
return render_template('interaction/edit_specialist.html', form=form, specialist_id=specialist_id)
|
||||
|
||||
|
||||
@interaction_bp.route('/specialists', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def specialists():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
query = Specialist.query.order_by(Specialist.id)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
the_specialists = pagination.items
|
||||
|
||||
# prepare table data
|
||||
rows = prepare_table_for_macro(the_specialists,
|
||||
[('id', ''), ('name', ''), ('type', '')])
|
||||
|
||||
# Render the catalogs in a template
|
||||
return render_template('interaction/specialists.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@interaction_bp.route('/handle_specialist_selection', methods=['POST'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def handle_specialist_selection():
|
||||
specialist_identification = request.form.get('selected_row')
|
||||
specialist_id = ast.literal_eval(specialist_identification).get('value')
|
||||
action = request.form.get('action')
|
||||
|
||||
if action == "edit_specialist":
|
||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id))
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
Reference in New Issue
Block a user