- Corrections to tenant, catalog, and tenant_make
- Clean-up of tenant elements - ensure the chat_client get's it's initial call rifht.
This commit is contained in:
@@ -45,6 +45,21 @@ class Specialist(db.Model):
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||
updated_by = db.Column(db.Integer, db.ForeignKey(User.id))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Specialist {self.id}: {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'type': self.type,
|
||||
'type_version': self.type_version,
|
||||
'configuration': self.configuration,
|
||||
'arguments': self.arguments,
|
||||
'active': self.active,
|
||||
}
|
||||
|
||||
|
||||
class EveAIAsset(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@@ -238,3 +253,14 @@ class SpecialistMagicLink(db.Model):
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SpecialistMagicLink {self.specialist_id} {self.magic_link_code}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'magic_link_code': self.magic_link_code,
|
||||
'valid_from': self.valid_from,
|
||||
'valid_to': self.valid_to,
|
||||
'specialist_args': self.specialist_args,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class Tenant(db.Model):
|
||||
|
||||
# language information
|
||||
default_language = db.Column(db.String(2), nullable=True)
|
||||
allowed_languages = db.Column(ARRAY(sa.String(2)), nullable=True)
|
||||
|
||||
# Entitlements
|
||||
currency = db.Column(db.String(20), nullable=True)
|
||||
@@ -63,7 +62,6 @@ class Tenant(db.Model):
|
||||
'timezone': self.timezone,
|
||||
'type': self.type,
|
||||
'default_language': self.default_language,
|
||||
'allowed_languages': self.allowed_languages,
|
||||
'currency': self.currency,
|
||||
'default_tenant_make_id': self.default_tenant_make_id,
|
||||
}
|
||||
@@ -198,6 +196,20 @@ class TenantMake(db.Model):
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||
updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TenantMake {self.id} for tenant {self.tenant_id}: {self.name}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'active': self.active,
|
||||
'website': self.website,
|
||||
'logo_url': self.logo_url,
|
||||
'chat_customisation_options': self.chat_customisation_options,
|
||||
}
|
||||
|
||||
|
||||
class Partner(db.Model):
|
||||
__bind_key__ = 'public'
|
||||
|
||||
@@ -220,3 +220,17 @@ class SpecialistServices:
|
||||
db.session.add(tool)
|
||||
current_app.logger.info(f"Created tool {tool.id} of type {tool_type}")
|
||||
return tool
|
||||
|
||||
@staticmethod
|
||||
def get_specialist_system_field(specialist_id, config_name, system_name):
|
||||
specialist = Specialist.query.get(specialist_id)
|
||||
if not specialist:
|
||||
raise ValueError(f"Specialist with ID {specialist_id} not found")
|
||||
config = cache_manager.specialists_config_cache.get_config(specialist.type, specialist.version)
|
||||
if not config:
|
||||
raise ValueError(f"No configuration found for {specialist.type} version {specialist.version}")
|
||||
potential_field = config.get(config_name, None)
|
||||
if potential_field:
|
||||
if potential_field.type == 'system' and potential_field.system_name == system_name:
|
||||
return specialist.configuration.get(config_name, None)
|
||||
return None
|
||||
|
||||
@@ -5,6 +5,17 @@ All notable changes to EveAI will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.3.4-alfa]
|
||||
|
||||
### Added
|
||||
- Introduction of Tenant Make
|
||||
- Introduction of 'system' type for dynamic attributes
|
||||
- Introduce Tenant Make to Traicie Specialists
|
||||
|
||||
### Changed
|
||||
- Enable Specialist 'activation' / 'deactivation'
|
||||
- Unique constraints introduced for Catalog Name (tenant level) and make name (public level)
|
||||
|
||||
## [2.3.3-alfa]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -8,7 +8,19 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_field_content(field, disabled=False, readonly=False, class='') %}
|
||||
{% if field.type == 'BooleanField' %}
|
||||
{# Check if this is a hidden input field, if so, render only the field without label #}
|
||||
{{ debug_to_console("Field Class: ", field.widget.__class__.__name__) }}
|
||||
{% if field.widget.__class__.__name__ == 'HiddenInput' %}
|
||||
{{ debug_to_console("Hidden Field: ", "Detected") }}
|
||||
{{ field(class="form-control " + class, disabled=disabled, readonly=readonly) }}
|
||||
{% if field.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif field.type == 'BooleanField' %}
|
||||
<div class="form-group">
|
||||
<div class="form-check form-switch">
|
||||
{{ field(class="form-check-input " + class, disabled=disabled, readonly=readonly, required=False) }}
|
||||
|
||||
@@ -26,7 +26,6 @@ def validate_catalog_name(form, field):
|
||||
|
||||
|
||||
class CatalogForm(FlaskForm):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_catalog_name])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
|
||||
@@ -141,7 +141,6 @@ class SpecialistMagicLinkForm(FlaskForm):
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
magic_link_code = StringField('Magic Link Code', validators=[DataRequired(), Length(max=55)], render_kw={'readonly': True})
|
||||
specialist_id = SelectField('Specialist', validators=[DataRequired()])
|
||||
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
|
||||
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
||||
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
|
||||
|
||||
@@ -155,10 +154,6 @@ class SpecialistMagicLinkForm(FlaskForm):
|
||||
# Dynamically populate the specialist field
|
||||
self.specialist_id.choices = [(specialist.id, specialist.name) for specialist in specialists]
|
||||
|
||||
# Dynamically populate the tenant_make field with None as first option
|
||||
tenant_makes = TenantMake.query.all()
|
||||
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
||||
|
||||
|
||||
class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
@@ -168,7 +163,6 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||
specialist_id = IntegerField('Specialist', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
specialist_name = StringField('Specialist Name', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
|
||||
tenant_make_name = StringField('Tenant Make Name', validators=[Optional()], render_kw={'readonly': True})
|
||||
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
||||
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
|
||||
|
||||
|
||||
@@ -705,6 +705,14 @@ def specialist_magic_link():
|
||||
new_spec_ml_tenant.magic_link_code = new_specialist_magic_link.magic_link_code
|
||||
new_spec_ml_tenant.tenant_id = tenant_id
|
||||
|
||||
# Define the make valid for this magic link
|
||||
make_id = SpecialistServices.get_specialist_system_field(new_specialist_magic_link.id,
|
||||
"make", "tenant_make")
|
||||
if make_id:
|
||||
new_spec_ml_tenant.tenant_make_id = make_id
|
||||
elif session.get('tenant').get('default_tenant_make_id'):
|
||||
new_spec_ml_tenant.tenant_make_id = session.get('tenant').get('default_tenant_make_id')
|
||||
|
||||
db.session.add(new_specialist_magic_link)
|
||||
db.session.add(new_spec_ml_tenant)
|
||||
|
||||
|
||||
@@ -20,7 +20,41 @@ class TenantForm(FlaskForm):
|
||||
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
|
||||
# language fields
|
||||
default_language = SelectField('Default Language', choices=[], validators=[DataRequired()])
|
||||
allowed_languages = SelectMultipleField('Allowed Languages', choices=[], validators=[DataRequired()])
|
||||
# invoicing fields
|
||||
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
|
||||
# Timezone
|
||||
timezone = SelectField('Timezone', choices=[], validators=[DataRequired()])
|
||||
|
||||
# For Super Users only - Allow to assign the tenant to the partner
|
||||
assign_to_partner = BooleanField('Assign to Partner', default=False)
|
||||
# Embedding variables
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TenantForm, self).__init__(*args, **kwargs)
|
||||
# initialise language fields
|
||||
self.default_language.choices = [(lang, lang.lower()) for lang in current_app.config['SUPPORTED_LANGUAGES']]
|
||||
# initialise currency field
|
||||
self.currency.choices = [(curr, curr) for curr in current_app.config['SUPPORTED_CURRENCIES']]
|
||||
# initialise timezone
|
||||
self.timezone.choices = [(tz, tz) for tz in pytz.common_timezones]
|
||||
# Initialize fallback algorithms
|
||||
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
|
||||
# Initialize default tenant make choices
|
||||
tenant_id = session.get('tenant', {}).get('id') if 'tenant' in session else None
|
||||
# Show field only for Super Users with partner in session
|
||||
if not current_user.has_roles('Super User') or 'partner' not in session:
|
||||
self._fields.pop('assign_to_partner', None)
|
||||
|
||||
|
||||
class EditTenantForm(FlaskForm):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=80)])
|
||||
code = StringField('Code', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
type = SelectField('Tenant Type', validators=[Optional()], default='Active')
|
||||
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
|
||||
# language fields
|
||||
default_language = SelectField('Default Language', choices=[], validators=[DataRequired()])
|
||||
# invoicing fields
|
||||
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
|
||||
# Timezone
|
||||
@@ -34,10 +68,9 @@ class TenantForm(FlaskForm):
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TenantForm, self).__init__(*args, **kwargs)
|
||||
super(EditTenantForm, self).__init__(*args, **kwargs)
|
||||
# initialise language fields
|
||||
self.default_language.choices = [(lang, lang.lower()) for lang in current_app.config['SUPPORTED_LANGUAGES']]
|
||||
self.allowed_languages.choices = [(lang, lang.lower()) for lang in current_app.config['SUPPORTED_LANGUAGES']]
|
||||
# initialise currency field
|
||||
self.currency.choices = [(curr, curr) for curr in current_app.config['SUPPORTED_CURRENCIES']]
|
||||
# initialise timezone
|
||||
@@ -45,7 +78,7 @@ class TenantForm(FlaskForm):
|
||||
# Initialize fallback algorithms
|
||||
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
|
||||
# Initialize default tenant make choices
|
||||
tenant_id = session.get('tenant', {}).get('id') if 'tenant' in session else None
|
||||
tenant_id = self.id.data
|
||||
if tenant_id:
|
||||
tenant_makes = TenantMake.query.filter_by(tenant_id=tenant_id, active=True).all()
|
||||
self.default_tenant_make_id.choices = [(str(make.id), make.name) for make in tenant_makes]
|
||||
@@ -154,6 +187,13 @@ def validate_make_name(form, field):
|
||||
|
||||
|
||||
class TenantMakeForm(DynamicFormBase):
|
||||
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)])
|
||||
logo_url = StringField('Logo URL', validators=[Optional(), Length(max=255)])
|
||||
|
||||
class EditTenantMakeForm(DynamicFormBase):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
@@ -12,7 +12,7 @@ from common.utils.dynamic_field_utils import create_default_config_from_type_con
|
||||
from common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||
from config.type_defs.service_types import SERVICE_TYPES
|
||||
from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm, TenantSelectionForm, \
|
||||
TenantProjectForm, EditTenantProjectForm, TenantMakeForm
|
||||
TenantProjectForm, EditTenantProjectForm, TenantMakeForm, EditTenantForm
|
||||
from common.utils.database import Database
|
||||
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
||||
from common.utils.simple_encryption import generate_api_key
|
||||
@@ -53,10 +53,6 @@ def tenant():
|
||||
new_tenant = Tenant()
|
||||
form.populate_obj(new_tenant)
|
||||
|
||||
# Convert default_tenant_make_id to integer if not empty
|
||||
if form.default_tenant_make_id.data:
|
||||
new_tenant.default_tenant_make_id = int(form.default_tenant_make_id.data)
|
||||
|
||||
timestamp = dt.now(tz.utc)
|
||||
new_tenant.created_at = timestamp
|
||||
new_tenant.updated_at = timestamp
|
||||
@@ -116,7 +112,7 @@ def tenant():
|
||||
@roles_accepted('Super User', 'Partner Admin')
|
||||
def edit_tenant(tenant_id):
|
||||
tenant = Tenant.query.get_or_404(tenant_id) # This will return a 404 if no tenant is found
|
||||
form = TenantForm(obj=tenant)
|
||||
form = EditTenantForm(obj=tenant)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Populate the tenant with form data
|
||||
@@ -471,7 +467,7 @@ def edit_tenant_domain(tenant_domain_id):
|
||||
def tenant_overview():
|
||||
tenant_id = session['tenant']['id']
|
||||
tenant = Tenant.query.get_or_404(tenant_id)
|
||||
form = TenantForm(obj=tenant)
|
||||
form = EditTenantForm(obj=tenant)
|
||||
|
||||
# Zet de waarde van default_tenant_make_id
|
||||
if tenant.default_tenant_make_id:
|
||||
@@ -683,7 +679,8 @@ def tenant_makes():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
query = TenantMake.query.order_by(TenantMake.id)
|
||||
tenant_id = session['tenant']['id']
|
||||
query = TenantMake.query.filter_by(tenant_id=tenant_id).order_by(TenantMake.id)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
tenant_makes = pagination.items
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from flask import Flask, jsonify
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
import logging.config
|
||||
|
||||
@@ -74,6 +75,13 @@ def create_app(config_file=None):
|
||||
|
||||
app.logger.info(f"EveAI Chat Client Started Successfully (PID: {os.getpid()})")
|
||||
app.logger.info("-------------------------------------------------------------------------------------------------")
|
||||
|
||||
# @app.before_request
|
||||
# def app_before_request():
|
||||
# app.logger.debug(f'App before request: {request.path} ===== Method: {request.method} =====')
|
||||
# app.logger.debug(f'Full URL: {request.url}')
|
||||
# app.logger.debug(f'Endpoint: {request.endpoint}')
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<!-- Custom theme colors from tenant settings -->
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: {{ customization.primary_color|default('#007bff') }};
|
||||
--secondary-color: {{ customization.secondary_color|default('#6c757d') }};
|
||||
--background-color: {{ customization.background_color|default('#ffffff') }};
|
||||
--text-color: {{ customization.text_color|default('#212529') }};
|
||||
--sidebar-color: {{ customization.sidebar_color|default('#f8f9fa') }};
|
||||
--primary-color: {{ customisation.primary_color|default('#007bff') }};
|
||||
--secondary-color: {{ customisation.secondary_color|default('#6c757d') }};
|
||||
--background-color: {{ customisation.background_color|default('#ffffff') }};
|
||||
--text-color: {{ customisation.text_color|default('#212529') }};
|
||||
--sidebar-color: {{ customisation.sidebar_color|default('#f8f9fa') }};
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -3,13 +3,32 @@ from flask import Blueprint, render_template, request, session, current_app, jso
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.extensions import db
|
||||
from common.models.user import Tenant, SpecialistMagicLinkTenant
|
||||
from common.models.user import Tenant, SpecialistMagicLinkTenant, TenantMake
|
||||
from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
|
||||
from common.services.interaction.specialist_services import SpecialistServices
|
||||
from common.utils.database import Database
|
||||
from common.utils.chat_utils import get_default_chat_customisation
|
||||
|
||||
chat_bp = Blueprint('chat', __name__)
|
||||
chat_bp = Blueprint('chat_bp', __name__, url_prefix='/chat')
|
||||
|
||||
@chat_bp.before_request
|
||||
def log_before_request():
|
||||
current_app.logger.debug(f'Before request: {request.path} =====================================')
|
||||
|
||||
|
||||
@chat_bp.after_request
|
||||
def log_after_request(response):
|
||||
return response
|
||||
|
||||
|
||||
# @chat_bp.before_request
|
||||
# def before_request():
|
||||
# try:
|
||||
# mw_before_request()
|
||||
# except Exception as e:
|
||||
# current_app.logger.error(f'Error switching schema in Document Blueprint: {e}')
|
||||
# raise
|
||||
|
||||
|
||||
@chat_bp.route('/')
|
||||
def index():
|
||||
@@ -31,14 +50,12 @@ def chat(magic_link_code):
|
||||
current_app.logger.error(f"Invalid magic link code: {magic_link_code}")
|
||||
return render_template('error.html', message="Invalid magic link code.")
|
||||
|
||||
tenant_id = magic_link_tenant.tenant_id
|
||||
|
||||
# Get tenant information
|
||||
tenant_id = magic_link_tenant.tenant_id
|
||||
tenant = Tenant.query.get(tenant_id)
|
||||
if not tenant:
|
||||
current_app.logger.error(f"Tenant not found for ID: {tenant_id}")
|
||||
return render_template('error.html', message="Tenant not found.")
|
||||
|
||||
# Switch to tenant schema
|
||||
Database(tenant_id).switch_schema()
|
||||
|
||||
@@ -48,6 +65,12 @@ def chat(magic_link_code):
|
||||
current_app.logger.error(f"Specialist magic link not found in tenant schema: {tenant_id}")
|
||||
return render_template('error.html', message="Specialist configuration not found.")
|
||||
|
||||
# Get relevant TenantMake
|
||||
tenant_make = TenantMake.query.get(specialist_ml.tenant_make_id)
|
||||
if not tenant_make:
|
||||
current_app.logger.error(f"Tenant make not found: {specialist_ml.tenant_make_id}")
|
||||
return render_template('error.html', message="Tenant make not found.")
|
||||
|
||||
# Get specialist details
|
||||
specialist = Specialist.query.get(specialist_ml.specialist_id)
|
||||
if not specialist:
|
||||
@@ -55,13 +78,13 @@ def chat(magic_link_code):
|
||||
return render_template('error.html', message="Specialist not found.")
|
||||
|
||||
# Store necessary information in session
|
||||
session['tenant_id'] = tenant_id
|
||||
session['specialist_id'] = specialist_ml.specialist_id
|
||||
session['specialist_args'] = specialist_ml.specialist_args or {}
|
||||
session['magic_link_code'] = magic_link_code
|
||||
session['tenant'] = tenant.to_dict()
|
||||
session['specialist'] = specialist.to_dict()
|
||||
session['magic_link'] = specialist_ml.to_dict()
|
||||
session['tenant_make'] = tenant_make.to_dict()
|
||||
|
||||
# Get customisation options with defaults
|
||||
customisation = get_default_chat_customisation(tenant.chat_customisation_options)
|
||||
customisation = get_default_chat_customisation(tenant_make.chat_customisation_options)
|
||||
|
||||
# Start a new chat session
|
||||
session['chat_session_id'] = SpecialistServices.start_session()
|
||||
|
||||
Reference in New Issue
Block a user