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_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))
|
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):
|
class EveAIAsset(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -238,3 +253,14 @@ class SpecialistMagicLink(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<SpecialistMagicLink {self.specialist_id} {self.magic_link_code}>"
|
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
|
# language information
|
||||||
default_language = db.Column(db.String(2), nullable=True)
|
default_language = db.Column(db.String(2), nullable=True)
|
||||||
allowed_languages = db.Column(ARRAY(sa.String(2)), nullable=True)
|
|
||||||
|
|
||||||
# Entitlements
|
# Entitlements
|
||||||
currency = db.Column(db.String(20), nullable=True)
|
currency = db.Column(db.String(20), nullable=True)
|
||||||
@@ -63,7 +62,6 @@ class Tenant(db.Model):
|
|||||||
'timezone': self.timezone,
|
'timezone': self.timezone,
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
'default_language': self.default_language,
|
'default_language': self.default_language,
|
||||||
'allowed_languages': self.allowed_languages,
|
|
||||||
'currency': self.currency,
|
'currency': self.currency,
|
||||||
'default_tenant_make_id': self.default_tenant_make_id,
|
'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_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'))
|
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):
|
class Partner(db.Model):
|
||||||
__bind_key__ = 'public'
|
__bind_key__ = 'public'
|
||||||
|
|||||||
@@ -220,3 +220,18 @@ class SpecialistServices:
|
|||||||
db.session.add(tool)
|
db.session.add(tool)
|
||||||
current_app.logger.info(f"Created tool {tool.id} of type {tool_type}")
|
current_app.logger.info(f"Created tool {tool.id} of type {tool_type}")
|
||||||
return tool
|
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):
|
def get_default_chat_customisation(tenant_customisation=None):
|
||||||
"""
|
"""
|
||||||
Get chat customization options with default values for missing options.
|
Get chat customization options with default values for missing options.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tenant_customization (dict, optional): The tenant's customization options.
|
tenant_customization (dict, optional): The tenant's customization options.
|
||||||
Defaults to None.
|
Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary containing all customization options with default values
|
dict: A dictionary containing all customization options with default values
|
||||||
for any missing options.
|
for any missing options.
|
||||||
@@ -21,22 +21,25 @@ def get_default_chat_customisation(tenant_customisation=None):
|
|||||||
'background_color': '#ffffff',
|
'background_color': '#ffffff',
|
||||||
'text_color': '#212529',
|
'text_color': '#212529',
|
||||||
'sidebar_color': '#f8f9fa',
|
'sidebar_color': '#f8f9fa',
|
||||||
'logo_url': None,
|
'sidebar_background': '#2c3e50',
|
||||||
'sidebar_text': None,
|
'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?',
|
'welcome_message': 'Hello! How can I help you today?',
|
||||||
'team_info': []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# If no tenant customization is provided, return the defaults
|
# If no tenant customization is provided, return the defaults
|
||||||
if tenant_customisation is None:
|
if tenant_customisation is None:
|
||||||
return default_customisation
|
return default_customisation
|
||||||
|
|
||||||
# Start with the default customization
|
# Start with the default customization
|
||||||
customisation = default_customisation.copy()
|
customisation = default_customisation.copy()
|
||||||
|
|
||||||
# Update with tenant customization
|
# Update with tenant customization
|
||||||
for key, value in tenant_customisation.items():
|
for key, value in tenant_customisation.items():
|
||||||
if key in customisation:
|
if key in customisation:
|
||||||
customisation[key] = value
|
customisation[key] = value
|
||||||
|
|
||||||
return customisation
|
return customisation
|
||||||
|
|||||||
@@ -26,9 +26,34 @@ configuration:
|
|||||||
description: "Sidebar Color"
|
description: "Sidebar Color"
|
||||||
type: "color"
|
type: "color"
|
||||||
required: false
|
required: false
|
||||||
"sidebar_text":
|
"sidebar_background":
|
||||||
name: "Sidebar Text"
|
name: "Sidebar Background"
|
||||||
description: "Text to be shown in the sidebar"
|
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"
|
type: "text"
|
||||||
required: false
|
required: false
|
||||||
"welcome_message":
|
"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/),
|
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).
|
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]
|
## [2.3.3-alfa]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -8,7 +8,19 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro render_field_content(field, disabled=False, readonly=False, class='') %}
|
{% 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-group">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
{{ field(class="form-check-input " + class, disabled=disabled, readonly=readonly, required=False) }}
|
{{ 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_security import current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SelectField
|
from wtforms import StringField, SelectField
|
||||||
@@ -36,7 +36,7 @@ class SessionDefaultsForm(FlaskForm):
|
|||||||
else:
|
else:
|
||||||
self.partner_name.data = ""
|
self.partner_name.data = ""
|
||||||
self.default_language.choices = [(lang, lang.lower()) for lang in
|
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')
|
self.default_language.data = session.get('default_language')
|
||||||
|
|
||||||
# Get a new session for catalog queries
|
# Get a new session for catalog queries
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ def validate_catalog_name(form, field):
|
|||||||
|
|
||||||
|
|
||||||
class CatalogForm(FlaskForm):
|
class CatalogForm(FlaskForm):
|
||||||
id = IntegerField('ID', widget=HiddenInput())
|
|
||||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_catalog_name])
|
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_catalog_name])
|
||||||
description = TextAreaField('Description', validators=[Optional()])
|
description = TextAreaField('Description', validators=[Optional()])
|
||||||
|
|
||||||
@@ -191,7 +190,7 @@ class AddDocumentForm(DynamicFormBase):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.language.choices = [(language, language) for language in
|
self.language.choices = [(language, language) for language in
|
||||||
session.get('tenant').get('allowed_languages')]
|
current_app.config['SUPPORTED_LANGUAGES']]
|
||||||
if not self.language.data:
|
if not self.language.data:
|
||||||
self.language.data = session.get('tenant').get('default_language')
|
self.language.data = session.get('tenant').get('default_language')
|
||||||
|
|
||||||
@@ -211,7 +210,7 @@ class AddURLForm(DynamicFormBase):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.language.choices = [(language, language) for language in
|
self.language.choices = [(language, language) for language in
|
||||||
session.get('tenant').get('allowed_languages')]
|
current_app.config['SUPPORTED_LANGUAGES']]
|
||||||
if not self.language.data:
|
if not self.language.data:
|
||||||
self.language.data = session.get('tenant').get('default_language')
|
self.language.data = session.get('tenant').get('default_language')
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ class SpecialistMagicLinkForm(FlaskForm):
|
|||||||
description = TextAreaField('Description', validators=[Optional()])
|
description = TextAreaField('Description', validators=[Optional()])
|
||||||
magic_link_code = StringField('Magic Link Code', validators=[DataRequired(), Length(max=55)], render_kw={'readonly': True})
|
magic_link_code = StringField('Magic Link Code', validators=[DataRequired(), Length(max=55)], render_kw={'readonly': True})
|
||||||
specialist_id = SelectField('Specialist', validators=[DataRequired()])
|
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_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
||||||
valid_to = DateField('Valid To', 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
|
# Dynamically populate the specialist field
|
||||||
self.specialist_id.choices = [(specialist.id, specialist.name) for specialist in specialists]
|
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):
|
class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
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_id = IntegerField('Specialist', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
specialist_name = StringField('Specialist Name', 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_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_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
||||||
valid_to = DateField('Valid To', 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()
|
tenant_makes = TenantMake.query.all()
|
||||||
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
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
|
# Populate fields individually instead of using populate_obj
|
||||||
form.populate_obj(new_specialist_magic_link)
|
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))
|
set_logging_information(new_specialist_magic_link, dt.now(tz.utc))
|
||||||
|
|
||||||
# Create 'public' SpecialistMagicLinkTenant
|
# 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.magic_link_code = new_specialist_magic_link.magic_link_code
|
||||||
new_spec_ml_tenant.tenant_id = tenant_id
|
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_specialist_magic_link)
|
||||||
db.session.add(new_spec_ml_tenant)
|
db.session.add(new_spec_ml_tenant)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,41 @@ class TenantForm(FlaskForm):
|
|||||||
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
|
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
|
||||||
# language fields
|
# language fields
|
||||||
default_language = SelectField('Default Language', choices=[], validators=[DataRequired()])
|
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
|
# invoicing fields
|
||||||
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
|
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
|
||||||
# Timezone
|
# Timezone
|
||||||
@@ -34,10 +68,9 @@ class TenantForm(FlaskForm):
|
|||||||
submit = SubmitField('Submit')
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(TenantForm, self).__init__(*args, **kwargs)
|
super(EditTenantForm, self).__init__(*args, **kwargs)
|
||||||
# initialise language fields
|
# initialise language fields
|
||||||
self.default_language.choices = [(lang, lang.lower()) for lang in current_app.config['SUPPORTED_LANGUAGES']]
|
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
|
# initialise currency field
|
||||||
self.currency.choices = [(curr, curr) for curr in current_app.config['SUPPORTED_CURRENCIES']]
|
self.currency.choices = [(curr, curr) for curr in current_app.config['SUPPORTED_CURRENCIES']]
|
||||||
# initialise timezone
|
# initialise timezone
|
||||||
@@ -45,7 +78,7 @@ class TenantForm(FlaskForm):
|
|||||||
# Initialize fallback algorithms
|
# Initialize fallback algorithms
|
||||||
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
|
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
|
||||||
# Initialize default tenant make choices
|
# 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:
|
if tenant_id:
|
||||||
tenant_makes = TenantMake.query.filter_by(tenant_id=tenant_id, active=True).all()
|
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]
|
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):
|
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()
|
existing_make = TenantMake.query.filter_by(name=field.data).first()
|
||||||
|
|
||||||
# Als er een bestaande make is gevonden en we zijn niet in edit mode,
|
if existing_make:
|
||||||
# of als we wel in edit mode zijn maar het is een ander record (andere id)
|
current_app.logger.debug(f'Existing make: {existing_make.id}')
|
||||||
if existing_make and (not hasattr(form, 'id') or form.id.data != existing_make.id):
|
current_app.logger.debug(f'Form has id: {hasattr(form, 'id')}')
|
||||||
raise ValidationError(f'A Make with name "{field.data}" already exists. Choose another name.')
|
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):
|
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())
|
id = IntegerField('ID', widget=HiddenInput())
|
||||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
|
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
|
||||||
description = TextAreaField('Description', validators=[Optional()])
|
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 common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||||
from config.type_defs.service_types import SERVICE_TYPES
|
from config.type_defs.service_types import SERVICE_TYPES
|
||||||
from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm, TenantSelectionForm, \
|
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.database import Database
|
||||||
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
||||||
from common.utils.simple_encryption import generate_api_key
|
from common.utils.simple_encryption import generate_api_key
|
||||||
@@ -53,10 +53,6 @@ def tenant():
|
|||||||
new_tenant = Tenant()
|
new_tenant = Tenant()
|
||||||
form.populate_obj(new_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)
|
timestamp = dt.now(tz.utc)
|
||||||
new_tenant.created_at = timestamp
|
new_tenant.created_at = timestamp
|
||||||
new_tenant.updated_at = timestamp
|
new_tenant.updated_at = timestamp
|
||||||
@@ -116,7 +112,7 @@ def tenant():
|
|||||||
@roles_accepted('Super User', 'Partner Admin')
|
@roles_accepted('Super User', 'Partner Admin')
|
||||||
def edit_tenant(tenant_id):
|
def edit_tenant(tenant_id):
|
||||||
tenant = Tenant.query.get_or_404(tenant_id) # This will return a 404 if no tenant is found
|
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():
|
if form.validate_on_submit():
|
||||||
# Populate the tenant with form data
|
# Populate the tenant with form data
|
||||||
@@ -471,7 +467,7 @@ def edit_tenant_domain(tenant_domain_id):
|
|||||||
def tenant_overview():
|
def tenant_overview():
|
||||||
tenant_id = session['tenant']['id']
|
tenant_id = session['tenant']['id']
|
||||||
tenant = Tenant.query.get_or_404(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
|
# Zet de waarde van default_tenant_make_id
|
||||||
if tenant.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)
|
page = request.args.get('page', 1, type=int)
|
||||||
per_page = request.args.get('per_page', 10, 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)
|
pagination = query.paginate(page=page, per_page=per_page)
|
||||||
tenant_makes = pagination.items
|
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)
|
tenant_make = TenantMake.query.get_or_404(tenant_make_id)
|
||||||
|
|
||||||
# Create form instance with the tenant make
|
# 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")
|
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
|
||||||
form.add_dynamic_fields("configuration", customisation_config, tenant_make.chat_customisation_options)
|
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
|
# Update session data if necessary
|
||||||
if 'tenant' in session:
|
if 'tenant' in session:
|
||||||
session['tenant'] = tenant.to_dict()
|
session['tenant'] = tenant.to_dict()
|
||||||
|
return None
|
||||||
|
return None
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash(f'Failed to update default tenant make. Error: {str(e)}', 'danger')
|
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 redirect(prefixed_url_for('user_bp.tenant_makes'))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def reset_uniquifier(user):
|
def reset_uniquifier(user):
|
||||||
security.datastore.set_uniquifier(user)
|
security.datastore.set_uniquifier(user)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from flask import Flask, jsonify
|
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
import logging.config
|
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(f"EveAI Chat Client Started Successfully (PID: {os.getpid()})")
|
||||||
app.logger.info("-------------------------------------------------------------------------------------------------")
|
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
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,28 +4,153 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}EveAI Chat{% endblock %}</title>
|
<title>{% block title %}EveAI Chat{% endblock %}</title>
|
||||||
|
|
||||||
<!-- CSS -->
|
<!-- CSS -->
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/chat.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 -->
|
<!-- Custom theme colors from tenant settings -->
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--primary-color: {{ customization.primary_color|default('#007bff') }};
|
--primary-color: {{ customisation.primary_color|default('#007bff') }};
|
||||||
--secondary-color: {{ customization.secondary_color|default('#6c757d') }};
|
--secondary-color: {{ customisation.secondary_color|default('#6c757d') }};
|
||||||
--background-color: {{ customization.background_color|default('#ffffff') }};
|
--background-color: {{ customisation.background_color|default('#ffffff') }};
|
||||||
--text-color: {{ customization.text_color|default('#212529') }};
|
--text-color: {{ customisation.text_color|default('#212529') }};
|
||||||
--sidebar-color: {{ customization.sidebar_color|default('#f8f9fa') }};
|
--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>
|
</style>
|
||||||
|
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div id="app" class="app-container">
|
||||||
{% block content %}{% endblock %}
|
<!-- 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>
|
</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 %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,13 +3,32 @@ from flask import Blueprint, render_template, request, session, current_app, jso
|
|||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from common.extensions import db
|
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.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
|
||||||
from common.services.interaction.specialist_services import SpecialistServices
|
from common.services.interaction.specialist_services import SpecialistServices
|
||||||
from common.utils.database import Database
|
from common.utils.database import Database
|
||||||
from common.utils.chat_utils import get_default_chat_customisation
|
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('/')
|
@chat_bp.route('/')
|
||||||
def index():
|
def index():
|
||||||
@@ -31,14 +50,12 @@ def chat(magic_link_code):
|
|||||||
current_app.logger.error(f"Invalid magic link code: {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.")
|
return render_template('error.html', message="Invalid magic link code.")
|
||||||
|
|
||||||
tenant_id = magic_link_tenant.tenant_id
|
|
||||||
|
|
||||||
# Get tenant information
|
# Get tenant information
|
||||||
|
tenant_id = magic_link_tenant.tenant_id
|
||||||
tenant = Tenant.query.get(tenant_id)
|
tenant = Tenant.query.get(tenant_id)
|
||||||
if not tenant:
|
if not tenant:
|
||||||
current_app.logger.error(f"Tenant not found for ID: {tenant_id}")
|
current_app.logger.error(f"Tenant not found for ID: {tenant_id}")
|
||||||
return render_template('error.html', message="Tenant not found.")
|
return render_template('error.html', message="Tenant not found.")
|
||||||
|
|
||||||
# Switch to tenant schema
|
# Switch to tenant schema
|
||||||
Database(tenant_id).switch_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}")
|
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.")
|
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
|
# Get specialist details
|
||||||
specialist = Specialist.query.get(specialist_ml.specialist_id)
|
specialist = Specialist.query.get(specialist_ml.specialist_id)
|
||||||
if not specialist:
|
if not specialist:
|
||||||
@@ -55,21 +78,22 @@ def chat(magic_link_code):
|
|||||||
return render_template('error.html', message="Specialist not found.")
|
return render_template('error.html', message="Specialist not found.")
|
||||||
|
|
||||||
# Store necessary information in session
|
# Store necessary information in session
|
||||||
session['tenant_id'] = tenant_id
|
session['tenant'] = tenant.to_dict()
|
||||||
session['specialist_id'] = specialist_ml.specialist_id
|
session['specialist'] = specialist.to_dict()
|
||||||
session['specialist_args'] = specialist_ml.specialist_args or {}
|
session['magic_link'] = specialist_ml.to_dict()
|
||||||
session['magic_link_code'] = magic_link_code
|
session['tenant_make'] = tenant_make.to_dict()
|
||||||
|
|
||||||
# Get customisation options with defaults
|
# 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
|
# Start a new chat session
|
||||||
session['chat_session_id'] = SpecialistServices.start_session()
|
session['chat_session_id'] = SpecialistServices.start_session()
|
||||||
|
|
||||||
return render_template('chat.html',
|
return render_template('chat.html',
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
specialist=specialist,
|
tenant_make=tenant_make,
|
||||||
customisation=customisation)
|
specialist=specialist,
|
||||||
|
customisation=customisation)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Error in chat view: {str(e)}", exc_info=True)
|
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',
|
'website': 'https://www.askeveai.com',
|
||||||
'timezone': 'UTC',
|
'timezone': 'UTC',
|
||||||
'default_language': 'en',
|
'default_language': 'en',
|
||||||
'allowed_languages': ['en', 'fr', 'nl', 'de', 'es'],
|
|
||||||
'type': 'Active',
|
'type': 'Active',
|
||||||
'currency': '€',
|
'currency': '€',
|
||||||
'created_at': dt.now(tz.utc),
|
'created_at': dt.now(tz.utc),
|
||||||
|
|||||||
Reference in New Issue
Block a user