Compare commits
3 Commits
v2.3.4-alf
...
v2.3.5-alf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67ceb57b79 | ||
|
|
23b49516cb | ||
|
|
9cc266b97f |
@@ -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,18 @@ 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):
|
||||
"""Get the value of a system field in a specialist's configuration. Returns the actual value, or None."""
|
||||
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.type_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,11 +5,11 @@ Utility functions for chat customization.
|
||||
def get_default_chat_customisation(tenant_customisation=None):
|
||||
"""
|
||||
Get chat customization options with default values for missing options.
|
||||
|
||||
|
||||
Args:
|
||||
tenant_customization (dict, optional): The tenant's customization options.
|
||||
Defaults to None.
|
||||
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing all customization options with default values
|
||||
for any missing options.
|
||||
@@ -21,22 +21,25 @@ def get_default_chat_customisation(tenant_customisation=None):
|
||||
'background_color': '#ffffff',
|
||||
'text_color': '#212529',
|
||||
'sidebar_color': '#f8f9fa',
|
||||
'logo_url': None,
|
||||
'sidebar_text': None,
|
||||
'sidebar_background': '#2c3e50',
|
||||
'gradient_start_color': '#f5f7fa',
|
||||
'gradient_end_color': '#c3cfe2',
|
||||
'markdown_background_color': 'transparent',
|
||||
'markdown_text_color': '#ffffff',
|
||||
'sidebar_markdown': '',
|
||||
'welcome_message': 'Hello! How can I help you today?',
|
||||
'team_info': []
|
||||
}
|
||||
|
||||
|
||||
# If no tenant customization is provided, return the defaults
|
||||
if tenant_customisation is None:
|
||||
return default_customisation
|
||||
|
||||
|
||||
# Start with the default customization
|
||||
customisation = default_customisation.copy()
|
||||
|
||||
|
||||
# Update with tenant customization
|
||||
for key, value in tenant_customisation.items():
|
||||
if key in customisation:
|
||||
customisation[key] = value
|
||||
|
||||
return customisation
|
||||
|
||||
return customisation
|
||||
|
||||
@@ -26,9 +26,34 @@ configuration:
|
||||
description: "Sidebar Color"
|
||||
type: "color"
|
||||
required: false
|
||||
"sidebar_text":
|
||||
name: "Sidebar Text"
|
||||
description: "Text to be shown in the sidebar"
|
||||
"sidebar_background":
|
||||
name: "Sidebar Background"
|
||||
description: "Sidebar Background Color"
|
||||
type: "color"
|
||||
required: false
|
||||
"markdown_background_color":
|
||||
name: "Markdown Background"
|
||||
description: "Markdown Background Color"
|
||||
type: "color"
|
||||
required: false
|
||||
"markdown_text_color":
|
||||
name: "Markdown Text"
|
||||
description: "Markdown Text Color"
|
||||
type: "color"
|
||||
required: false
|
||||
"gradient_start_color":
|
||||
name: "Gradient Start Color"
|
||||
description: "Start Color for the gradient in the Chat Area"
|
||||
type: "color"
|
||||
required: false
|
||||
"gradient_end_color":
|
||||
name: "Gradient End Color"
|
||||
description: "End Color for the gradient in the Chat Area"
|
||||
type: "color"
|
||||
required: false
|
||||
"sidebar_markdown":
|
||||
name: "Sidebar Markdown"
|
||||
description: "Sidebar Markdown-formatted Text"
|
||||
type: "text"
|
||||
required: false
|
||||
"welcome_message":
|
||||
|
||||
@@ -5,6 +5,31 @@ 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.5-alfa]
|
||||
|
||||
### Added
|
||||
- Chat Client Initialisation (based on SpecialistMagicLink code)
|
||||
- Definition of framework for the chat_client (using vue.js)
|
||||
|
||||
### Changed
|
||||
- Remove AllowedLanguages from Tenant
|
||||
- Remove Tenant URL (now in Make)
|
||||
- Adapt chat client customisation options
|
||||
|
||||
### Fixed
|
||||
- Several Bugfixes to administrative app
|
||||
|
||||
## [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) }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import session
|
||||
from flask import session, current_app
|
||||
from flask_security import current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SelectField
|
||||
@@ -36,7 +36,7 @@ class SessionDefaultsForm(FlaskForm):
|
||||
else:
|
||||
self.partner_name.data = ""
|
||||
self.default_language.choices = [(lang, lang.lower()) for lang in
|
||||
session.get('tenant').get('allowed_languages')]
|
||||
current_app.config['SUPPORTED_LANGUAGES']]
|
||||
self.default_language.data = session.get('default_language')
|
||||
|
||||
# Get a new session for catalog queries
|
||||
|
||||
@@ -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()])
|
||||
|
||||
@@ -191,7 +190,7 @@ class AddDocumentForm(DynamicFormBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.language.choices = [(language, language) for language in
|
||||
session.get('tenant').get('allowed_languages')]
|
||||
current_app.config['SUPPORTED_LANGUAGES']]
|
||||
if not self.language.data:
|
||||
self.language.data = session.get('tenant').get('default_language')
|
||||
|
||||
@@ -211,7 +210,7 @@ class AddURLForm(DynamicFormBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.language.choices = [(language, language) for language in
|
||||
session.get('tenant').get('allowed_languages')]
|
||||
current_app.config['SUPPORTED_LANGUAGES']]
|
||||
if not self.language.data:
|
||||
self.language.data = session.get('tenant').get('default_language')
|
||||
|
||||
|
||||
@@ -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()])
|
||||
|
||||
@@ -188,11 +182,6 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||
tenant_makes = TenantMake.query.all()
|
||||
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
||||
|
||||
# If the form has a tenant_make_id that's not zero, set the tenant_make_name
|
||||
if hasattr(self, 'tenant_make_id') and self.tenant_make_id.data and self.tenant_make_id.data > 0:
|
||||
tenant_make = TenantMake.query.get(self.tenant_make_id.data)
|
||||
if tenant_make:
|
||||
self.tenant_make_name.data = tenant_make.name
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -694,10 +694,6 @@ def specialist_magic_link():
|
||||
# Populate fields individually instead of using populate_obj
|
||||
form.populate_obj(new_specialist_magic_link)
|
||||
|
||||
# Handle the tenant_make_id special case (0 = None)
|
||||
if form.tenant_make_id.data == 0:
|
||||
new_specialist_magic_link.tenant_make_id = None
|
||||
|
||||
set_logging_information(new_specialist_magic_link, dt.now(tz.utc))
|
||||
|
||||
# Create 'public' SpecialistMagicLinkTenant
|
||||
@@ -705,6 +701,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.specialist_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]
|
||||
@@ -144,16 +177,27 @@ class EditTenantProjectForm(FlaskForm):
|
||||
|
||||
|
||||
def validate_make_name(form, field):
|
||||
# Controleer of een TenantMake met deze naam al bestaat
|
||||
# Check if tenant_make already exists in the database
|
||||
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.')
|
||||
if existing_make:
|
||||
current_app.logger.debug(f'Existing make: {existing_make.id}')
|
||||
current_app.logger.debug(f'Form has id: {hasattr(form, 'id')}')
|
||||
if hasattr(form, 'id'):
|
||||
current_app.logger.debug(f'Form has id: {form.id.data}')
|
||||
if existing_make:
|
||||
if 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), 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, EditTenantMakeForm
|
||||
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
|
||||
@@ -704,7 +701,7 @@ def edit_tenant_make(tenant_make_id):
|
||||
tenant_make = TenantMake.query.get_or_404(tenant_make_id)
|
||||
|
||||
# Create form instance with the tenant make
|
||||
form = TenantMakeForm(request.form, obj=tenant_make)
|
||||
form = EditTenantMakeForm(request.form, obj=tenant_make)
|
||||
|
||||
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
|
||||
form.add_dynamic_fields("configuration", customisation_config, tenant_make.chat_customisation_options)
|
||||
@@ -758,6 +755,8 @@ def handle_tenant_make_selection():
|
||||
# Update session data if necessary
|
||||
if 'tenant' in session:
|
||||
session['tenant'] = tenant.to_dict()
|
||||
return None
|
||||
return None
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to update default tenant make. Error: {str(e)}', 'danger')
|
||||
@@ -765,6 +764,9 @@ def handle_tenant_make_selection():
|
||||
|
||||
return redirect(prefixed_url_for('user_bp.tenant_makes'))
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def reset_uniquifier(user):
|
||||
security.datastore.set_uniquifier(user)
|
||||
db.session.add(user)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -4,28 +4,153 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}EveAI Chat{% endblock %}</title>
|
||||
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/chat.css') }}">
|
||||
|
||||
|
||||
<!-- Vue.js -->
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
|
||||
<!-- Markdown parser for explanation text -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- 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') }};
|
||||
--sidebar-background: {{ customisation.sidebar_background|default('#2c3e50') }};
|
||||
--gradient-start-color: {{ customisation.gradient_start_color|default('#f5f7fa') }};
|
||||
--gradient-end-color: {{ customisation.gradient_end_color|default('#c3cfe2') }};
|
||||
--markdown-background-color: {{ customisation.markdown_background_color|default('transparent') }};
|
||||
--markdown-text-color: {{ customisation.markdown_text_color|default('#ffffff') }};
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 300px;
|
||||
background-color: var(--sidebar-background);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar-logo img {
|
||||
max-width: 100%;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.sidebar-make-name {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-explanation {
|
||||
margin-top: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--markdown-background-color);
|
||||
color: var(--markdown-text-color);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Ensure all elements in the markdown content inherit the text color */
|
||||
.sidebar-explanation * {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Style links in the markdown content */
|
||||
.sidebar-explanation a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, var(--gradient-start-color), var(--gradient-end-color));
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
<div id="app" class="app-container">
|
||||
<!-- Left sidebar - never changes -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<img src="{{ tenant_make.logo_url|default('') }}" alt="{{ tenant_make.name|default('Logo') }}">
|
||||
</div>
|
||||
<div class="sidebar-make-name">
|
||||
{{ tenant_make.name|default('') }}
|
||||
</div>
|
||||
<div class="sidebar-explanation" v-html="compiledExplanation"></div>
|
||||
</div>
|
||||
|
||||
<!-- Right content area - contains the chat client -->
|
||||
<div class="content-area">
|
||||
<div class="chat-container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
explanation: `{{ customisation.sidebar_markdown|default('') }}`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
compiledExplanation: function() {
|
||||
// Handle different versions of the marked library
|
||||
if (typeof marked === 'function') {
|
||||
return marked(this.explanation);
|
||||
} else if (marked && typeof marked.parse === 'function') {
|
||||
return marked.parse(this.explanation);
|
||||
} else {
|
||||
console.error('Marked library not properly loaded');
|
||||
return this.explanation; // Fallback to raw text
|
||||
}
|
||||
}
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -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,21 +78,22 @@ 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()
|
||||
|
||||
return render_template('chat.html',
|
||||
tenant=tenant,
|
||||
specialist=specialist,
|
||||
customisation=customisation)
|
||||
return render_template('chat.html',
|
||||
tenant=tenant,
|
||||
tenant_make=tenant_make,
|
||||
specialist=specialist,
|
||||
customisation=customisation)
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error in chat view: {str(e)}", exc_info=True)
|
||||
|
||||
@@ -82,7 +82,6 @@ def initialize_default_tenant():
|
||||
'website': 'https://www.askeveai.com',
|
||||
'timezone': 'UTC',
|
||||
'default_language': 'en',
|
||||
'allowed_languages': ['en', 'fr', 'nl', 'de', 'es'],
|
||||
'type': 'Active',
|
||||
'currency': '€',
|
||||
'created_at': dt.now(tz.utc),
|
||||
|
||||
Reference in New Issue
Block a user