Compare commits
3 Commits
v2.3.3-alf
...
v2.3.4-alf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f77871c4f | ||
|
|
199cf94cf2 | ||
|
|
c4dcd6a0d3 |
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ..extensions import db
|
||||
from .user import User, Tenant
|
||||
from .user import User, Tenant, TenantMake
|
||||
from .document import Embedding, Retriever
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class Specialist(db.Model):
|
||||
tuning = db.Column(db.Boolean, nullable=True, default=False)
|
||||
configuration = db.Column(JSONB, nullable=True)
|
||||
arguments = db.Column(JSONB, nullable=True)
|
||||
active = db.Column(db.Boolean, nullable=True, default=True)
|
||||
|
||||
# Relationship to retrievers through the association table
|
||||
retrievers = db.relationship('SpecialistRetriever', backref='specialist', lazy=True,
|
||||
@@ -222,6 +223,7 @@ class SpecialistMagicLink(db.Model):
|
||||
name = db.Column(db.String(50), nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
specialist_id = db.Column(db.Integer, db.ForeignKey(Specialist.id, ondelete='CASCADE'), nullable=False)
|
||||
tenant_make_id = db.Column(db.Integer, db.ForeignKey(TenantMake.id, ondelete='CASCADE'), nullable=True)
|
||||
magic_link_code = db.Column(db.String(55), nullable=False, unique=True)
|
||||
|
||||
valid_from = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
@@ -33,12 +33,15 @@ class Tenant(db.Model):
|
||||
# Entitlements
|
||||
currency = db.Column(db.String(20), nullable=True)
|
||||
storage_dirty = db.Column(db.Boolean, nullable=True, default=False)
|
||||
default_tenant_make_id = db.Column(db.Integer, db.ForeignKey('public.tenant_make.id'), nullable=True)
|
||||
|
||||
# Relations
|
||||
users = db.relationship('User', backref='tenant')
|
||||
domains = db.relationship('TenantDomain', backref='tenant')
|
||||
licenses = db.relationship('License', back_populates='tenant')
|
||||
license_usages = db.relationship('LicenseUsage', backref='tenant')
|
||||
tenant_makes = db.relationship('TenantMake', backref='tenant', foreign_keys='TenantMake.tenant_id')
|
||||
default_tenant_make = db.relationship('TenantMake', foreign_keys=[default_tenant_make_id], uselist=False)
|
||||
|
||||
@property
|
||||
def current_license(self):
|
||||
@@ -62,6 +65,7 @@ class Tenant(db.Model):
|
||||
'default_language': self.default_language,
|
||||
'allowed_languages': self.allowed_languages,
|
||||
'currency': self.currency,
|
||||
'default_tenant_make_id': self.default_tenant_make_id,
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +183,7 @@ class TenantMake(db.Model):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||
name = db.Column(db.String(50), nullable=False)
|
||||
name = db.Column(db.String(50), nullable=False, unique=True)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
active = db.Column(db.Boolean, nullable=False, default=True)
|
||||
website = db.Column(db.String(255), nullable=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: "1.1.0"
|
||||
version: "1.2.0"
|
||||
name: "Traicie Role Definition Specialist"
|
||||
framework: "crewai"
|
||||
partner: "traicie"
|
||||
@@ -11,9 +11,9 @@ arguments:
|
||||
type: "str"
|
||||
required: true
|
||||
specialist_name:
|
||||
name: "Specialist Name"
|
||||
description: "The name the specialist will be called upon"
|
||||
type: str
|
||||
name: "Chatbot Name"
|
||||
description: "The name of the chatbot."
|
||||
type: "str"
|
||||
required: true
|
||||
role_reference:
|
||||
name: "Role Reference"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
version: "1.3.0"
|
||||
name: "Traicie Role Definition Specialist"
|
||||
framework: "crewai"
|
||||
partner: "traicie"
|
||||
chat: false
|
||||
configuration: {}
|
||||
arguments:
|
||||
role_name:
|
||||
name: "Role Name"
|
||||
description: "The name of the role that is being processed. Will be used to create a selection specialist"
|
||||
type: "str"
|
||||
required: true
|
||||
specialist_name:
|
||||
name: "Chatbot Name"
|
||||
description: "The name of the chatbot."
|
||||
type: "str"
|
||||
required: true
|
||||
make:
|
||||
name: "Make"
|
||||
description: "The make for which the role is defined and the selection specialist is created"
|
||||
type: "system"
|
||||
system_name: "tenant_make"
|
||||
required: true
|
||||
role_reference:
|
||||
name: "Role Reference"
|
||||
description: "A customer reference to the role"
|
||||
type: "str"
|
||||
required: false
|
||||
vacancy_text:
|
||||
name: "vacancy_text"
|
||||
type: "text"
|
||||
description: "The Vacancy Text"
|
||||
required: true
|
||||
results:
|
||||
competencies:
|
||||
name: "competencies"
|
||||
type: "List[str, str]"
|
||||
description: "List of vacancy competencies and their descriptions"
|
||||
required: false
|
||||
agents:
|
||||
- type: "TRAICIE_HR_BP_AGENT"
|
||||
version: "1.0"
|
||||
tasks:
|
||||
- type: "TRAICIE_GET_COMPETENCIES_TASK"
|
||||
version: "1.1"
|
||||
metadata:
|
||||
author: "Josako"
|
||||
date_added: "2025-05-27"
|
||||
changes: "Added a make to be specified (a selection specialist now is created in context of a make"
|
||||
description: "Assistant to create a new Vacancy based on Vacancy Text"
|
||||
@@ -0,0 +1,120 @@
|
||||
version: "1.1.0"
|
||||
name: "Traicie Selection Specialist"
|
||||
framework: "crewai"
|
||||
partner: "traicie"
|
||||
chat: false
|
||||
configuration:
|
||||
name:
|
||||
name: "Name"
|
||||
description: "The name the specialist is called upon."
|
||||
type: "str"
|
||||
required: true
|
||||
role_reference:
|
||||
name: "Role Reference"
|
||||
description: "A customer reference to the role"
|
||||
type: "str"
|
||||
required: false
|
||||
make:
|
||||
name: "Make"
|
||||
description: "The make for which the role is defined and the selection specialist is created"
|
||||
type: "system"
|
||||
system_name: "tenant_make"
|
||||
required: true
|
||||
competencies:
|
||||
name: "Competencies"
|
||||
description: "An ordered list of competencies."
|
||||
type: "ordered_list"
|
||||
list_type: "competency_details"
|
||||
required: true
|
||||
tone_of_voice:
|
||||
name: "Tone of Voice"
|
||||
description: "The tone of voice the specialist uses to communicate"
|
||||
type: "enum"
|
||||
allowed_values: ["Professional & Neutral", "Warm & Empathetic", "Energetic & Enthusiastic", "Accessible & Informal", "Expert & Trustworthy", "No-nonsense & Goal-driven"]
|
||||
default: "Professional & Neutral"
|
||||
required: true
|
||||
language_level:
|
||||
name: "Language Level"
|
||||
description: "Language level to be used when communicating, relating to CEFR levels"
|
||||
type: "enum"
|
||||
allowed_values: ["Basic", "Standard", "Professional"]
|
||||
default: "Standard"
|
||||
required: true
|
||||
welcome_message:
|
||||
name: "Welcome Message"
|
||||
description: "Introductory text given by the specialist - but translated according to Tone of Voice, Language Level and Starting Language"
|
||||
type: "text"
|
||||
required: false
|
||||
closing_message:
|
||||
name: "Closing Message"
|
||||
description: "Closing message given by the specialist - but translated according to Tone of Voice, Language Level and Starting Language"
|
||||
type: "text"
|
||||
required: false
|
||||
competency_details:
|
||||
title:
|
||||
name: "Title"
|
||||
description: "Competency Title"
|
||||
type: "str"
|
||||
required: true
|
||||
description:
|
||||
name: "Description"
|
||||
description: "Description (in context of the role) of the competency"
|
||||
type: "text"
|
||||
required: true
|
||||
is_knockout:
|
||||
name: "KO"
|
||||
description: "Defines if the competency is a knock-out criterium"
|
||||
type: "boolean"
|
||||
required: true
|
||||
default: false
|
||||
assess:
|
||||
name: "Assess"
|
||||
description: "Indication if this competency is to be assessed"
|
||||
type: "boolean"
|
||||
required: true
|
||||
default: true
|
||||
arguments:
|
||||
region:
|
||||
name: "Region"
|
||||
type: "str"
|
||||
description: "The region of the specific vacancy"
|
||||
required: false
|
||||
working_schedule:
|
||||
name: "Work Schedule"
|
||||
type: "str"
|
||||
description: "The work schedule or employment type of the specific vacancy"
|
||||
required: false
|
||||
start_date:
|
||||
name: "Start Date"
|
||||
type: "date"
|
||||
description: "The start date of the specific vacancy"
|
||||
required: false
|
||||
language:
|
||||
name: "Language"
|
||||
type: "str"
|
||||
description: "The language (2-letter code) used to start the conversation"
|
||||
required: true
|
||||
interaction_mode:
|
||||
name: "Interaction Mode"
|
||||
type: "enum"
|
||||
description: "The interaction mode the specialist will start working in."
|
||||
allowed_values: ["Job Application", "Seduction"]
|
||||
default: "Job Application"
|
||||
required: true
|
||||
results:
|
||||
competencies:
|
||||
name: "competencies"
|
||||
type: "List[str, str]"
|
||||
description: "List of vacancy competencies and their descriptions"
|
||||
required: false
|
||||
agents:
|
||||
- type: "TRAICIE_HR_BP_AGENT"
|
||||
version: "1.0"
|
||||
tasks:
|
||||
- type: "TRAICIE_GET_COMPETENCIES_TASK"
|
||||
version: "1.1"
|
||||
metadata:
|
||||
author: "Josako"
|
||||
date_added: "2025-05-27"
|
||||
changes: "Add make to the selection specialist"
|
||||
description: "Assistant to create a new Vacancy based on Vacancy Text"
|
||||
@@ -10,7 +10,7 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form method="POST" action="{{ url_for('interaction_bp.handle_specialist_selection') }}" id="specialistsForm">
|
||||
{{ render_selectable_table(headers=["Specialist ID", "Name", "Type"], rows=rows, selectable=True, id="specialistsTable") }}
|
||||
{{ render_selectable_table(headers=["Specialist ID", "Name", "Type", "Type Version", "Active"], rows=rows, selectable=True, id="specialistsTable") }}
|
||||
<div class="form-group mt-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<button type="submit" name="action" value="edit_specialist" class="btn btn-primary" onclick="return validateTableSelection('specialistsForm')">Edit Specialist</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ from wtforms.validators import DataRequired, Length, Optional, URL, ValidationEr
|
||||
from flask_wtf.file import FileField, FileRequired
|
||||
import json
|
||||
|
||||
from wtforms.widgets.core import HiddenInput
|
||||
from wtforms_sqlalchemy.fields import QuerySelectField
|
||||
|
||||
from common.extensions import cache_manager
|
||||
@@ -20,11 +21,12 @@ from .dynamic_form_base import DynamicFormBase
|
||||
def validate_catalog_name(form, field):
|
||||
# Controleer of een catalog met deze naam al bestaat
|
||||
existing_catalog = Catalog.query.filter_by(name=field.data).first()
|
||||
if existing_catalog:
|
||||
if existing_catalog and (not hasattr(form, 'id') or form.id.data != existing_catalog.id):
|
||||
raise ValidationError(f'A Catalog with name "{field.data}" already exists. Choose another name.')
|
||||
|
||||
|
||||
class CatalogForm(FlaskForm):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_catalog_name])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
@@ -48,6 +50,7 @@ class CatalogForm(FlaskForm):
|
||||
|
||||
|
||||
class EditCatalogForm(DynamicFormBase):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_catalog_name])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ from datetime import date
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (IntegerField, FloatField, BooleanField, StringField, TextAreaField, FileField,
|
||||
validators, ValidationError)
|
||||
from flask import current_app, request
|
||||
from flask import current_app, request, session
|
||||
import json
|
||||
|
||||
from wtforms.fields.choices import SelectField
|
||||
from wtforms.fields.datetime import DateField
|
||||
from wtforms.fields.simple import ColorField
|
||||
|
||||
from common.models.user import TenantMake
|
||||
from common.utils.config_field_types import TaggingFields, json_to_patterns, patterns_to_json
|
||||
|
||||
|
||||
@@ -300,6 +302,22 @@ class DynamicFormBase(FlaskForm):
|
||||
except Exception as e:
|
||||
raise ValidationError(f"Invalid ordered list: {str(e)}")
|
||||
|
||||
def _get_system_field(self, system_name):
|
||||
"""Get the field class and kwargs for a system field. Add system field cases as you need them."""
|
||||
field_class = None
|
||||
extra_classes = ''
|
||||
field_kwargs = {}
|
||||
match system_name:
|
||||
case 'tenant_make':
|
||||
field_class = SelectField
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
|
||||
choices = [(make.name, make.name) for make in makes]
|
||||
extra_classes = ''
|
||||
field_kwargs = {'choices': choices}
|
||||
|
||||
return field_class, extra_classes, field_kwargs
|
||||
|
||||
def add_dynamic_fields(self, collection_name, config, initial_data=None):
|
||||
"""Add dynamic fields to the form based on the configuration.
|
||||
|
||||
@@ -357,11 +375,12 @@ class DynamicFormBase(FlaskForm):
|
||||
extra_classes = ['monospace-text', 'pattern-input']
|
||||
field_kwargs = {}
|
||||
elif field_type == 'ordered_list':
|
||||
current_app.logger.debug(f"Adding ordered list field for {full_field_name}")
|
||||
field_class = OrderedListField
|
||||
extra_classes = ''
|
||||
list_type = field_def.get('list_type', '')
|
||||
field_kwargs = {'list_type': list_type}
|
||||
elif field_type == 'system':
|
||||
field_class, extra_classes, field_kwargs = self._get_system_field(field_def.get('system_name', ''))
|
||||
else:
|
||||
extra_classes = ''
|
||||
field_class = {
|
||||
|
||||
@@ -8,6 +8,7 @@ from wtforms_sqlalchemy.fields import QuerySelectMultipleField
|
||||
|
||||
from common.models.document import Retriever
|
||||
from common.models.interaction import EveAITool, Specialist
|
||||
from common.models.user import TenantMake
|
||||
from common.extensions import cache_manager
|
||||
from common.utils.form_assistants import validate_json
|
||||
|
||||
@@ -24,6 +25,7 @@ def get_tools():
|
||||
|
||||
class SpecialistForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
retrievers = QuerySelectMultipleField(
|
||||
'Retrievers',
|
||||
@@ -34,7 +36,7 @@ class SpecialistForm(FlaskForm):
|
||||
)
|
||||
|
||||
type = SelectField('Specialist Type', validators=[DataRequired()])
|
||||
|
||||
active = BooleanField('Active', validators=[Optional()], default=True)
|
||||
tuning = BooleanField('Enable Specialist Tuning', default=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -47,6 +49,7 @@ class SpecialistForm(FlaskForm):
|
||||
class EditSpecialistForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired()])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
active = BooleanField('Active', validators=[Optional()], default=True)
|
||||
|
||||
retrievers = QuerySelectMultipleField(
|
||||
'Retrievers',
|
||||
@@ -138,6 +141,7 @@ 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()])
|
||||
|
||||
@@ -148,9 +152,13 @@ class SpecialistMagicLinkForm(FlaskForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
specialists = Specialist.query.all()
|
||||
# Dynamically populate the 'type' field using the constructor
|
||||
# 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)])
|
||||
@@ -159,6 +167,8 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||
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})
|
||||
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()])
|
||||
|
||||
@@ -174,5 +184,15 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
||||
else:
|
||||
self.specialist_name.data = ''
|
||||
|
||||
# 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]
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ def specialist():
|
||||
new_specialist.type = form.type.data
|
||||
new_specialist.type_version = cache_manager.specialists_version_tree_cache.get_latest_version(
|
||||
new_specialist.type)
|
||||
new_specialist.active = form.active.data
|
||||
new_specialist.tuning = form.tuning.data
|
||||
|
||||
set_logging_information(new_specialist, dt.now(tz.utc))
|
||||
@@ -231,6 +232,7 @@ def edit_specialist(specialist_id):
|
||||
specialist.name = form.name.data
|
||||
specialist.description = form.description.data
|
||||
specialist.tuning = form.tuning.data
|
||||
specialist.active = form.active.data
|
||||
# Update the configuration dynamic fields
|
||||
specialist.configuration = form.get_dynamic_data("configuration")
|
||||
|
||||
@@ -297,7 +299,7 @@ def specialists():
|
||||
|
||||
# prepare table data
|
||||
rows = prepare_table_for_macro(the_specialists,
|
||||
[('id', ''), ('name', ''), ('type', '')])
|
||||
[('id', ''), ('name', ''), ('type', ''), ('type_version', ''), ('active', ''),])
|
||||
|
||||
# Render the catalogs in a template
|
||||
return render_template('interaction/specialists.html', rows=rows, pagination=pagination)
|
||||
@@ -689,9 +691,13 @@ def specialist_magic_link():
|
||||
try:
|
||||
new_specialist_magic_link = SpecialistMagicLink()
|
||||
|
||||
# Populate fields individually instead of using populate_obj (gives problem with QueryMultipleSelectField)
|
||||
# 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
|
||||
@@ -731,12 +737,23 @@ def edit_specialist_magic_link(specialist_magic_link_id):
|
||||
|
||||
form.add_dynamic_fields("arguments", specialist_config, specialist_ml.specialist_args)
|
||||
|
||||
# Set the tenant_make_id default value
|
||||
if request.method == 'GET':
|
||||
if specialist_ml.tenant_make_id is None:
|
||||
form.tenant_make_id.data = 0
|
||||
else:
|
||||
form.tenant_make_id.data = specialist_ml.tenant_make_id
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update the basic fields
|
||||
form.populate_obj(specialist_ml)
|
||||
# Update the arguments dynamic fields
|
||||
specialist_ml.specialist_args = form.get_dynamic_data("arguments")
|
||||
|
||||
# Handle the tenant_make_id special case (0 = None)
|
||||
if form.tenant_make_id.data == 0:
|
||||
specialist_ml.tenant_make_id = None
|
||||
|
||||
# Update logging information
|
||||
update_logging_information(specialist_ml, dt.now(tz.utc))
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ from flask import current_app, session
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (StringField, BooleanField, SubmitField, EmailField, IntegerField, DateField,
|
||||
SelectField, SelectMultipleField, FieldList, FormField, TextAreaField)
|
||||
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional
|
||||
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, ValidationError
|
||||
import pytz
|
||||
from flask_security import current_user
|
||||
from wtforms.widgets.core import HiddenInput
|
||||
|
||||
from common.models.user import TenantMake
|
||||
from common.services.user import UserServices
|
||||
from config.type_defs.service_types import SERVICE_TYPES
|
||||
from eveai_app.views.dynamic_form_base import DynamicFormBase
|
||||
@@ -23,6 +25,8 @@ class TenantForm(FlaskForm):
|
||||
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
|
||||
# Timezone
|
||||
timezone = SelectField('Timezone', choices=[], validators=[DataRequired()])
|
||||
# Default tenant make
|
||||
default_tenant_make_id = SelectField('Default Tenant Make', choices=[], validators=[Optional()])
|
||||
|
||||
# For Super Users only - Allow to assign the tenant to the partner
|
||||
assign_to_partner = BooleanField('Assign to Partner', default=False)
|
||||
@@ -40,6 +44,13 @@ class TenantForm(FlaskForm):
|
||||
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
|
||||
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]
|
||||
# Add empty choice
|
||||
self.default_tenant_make_id.choices.insert(0, ('', 'Geen'))
|
||||
# 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)
|
||||
@@ -132,8 +143,19 @@ class EditTenantProjectForm(FlaskForm):
|
||||
self.services.choices = [(key, value['description']) for key, value in SERVICE_TYPES.items()]
|
||||
|
||||
|
||||
def validate_make_name(form, field):
|
||||
# Controleer of een TenantMake met deze naam al bestaat
|
||||
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.')
|
||||
|
||||
|
||||
class TenantMakeForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
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)])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
|
||||
@@ -52,6 +53,10 @@ 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
|
||||
@@ -117,6 +122,12 @@ def edit_tenant(tenant_id):
|
||||
# Populate the tenant with form data
|
||||
form.populate_obj(tenant)
|
||||
|
||||
# Convert default_tenant_make_id to integer if not empty
|
||||
if form.default_tenant_make_id.data:
|
||||
tenant.default_tenant_make_id = int(form.default_tenant_make_id.data)
|
||||
else:
|
||||
tenant.default_tenant_make_id = None
|
||||
|
||||
db.session.commit()
|
||||
flash('Tenant updated successfully.', 'success')
|
||||
if session.get('tenant'):
|
||||
@@ -461,7 +472,17 @@ def tenant_overview():
|
||||
tenant_id = session['tenant']['id']
|
||||
tenant = Tenant.query.get_or_404(tenant_id)
|
||||
form = TenantForm(obj=tenant)
|
||||
return render_template('user/tenant_overview.html', form=form)
|
||||
|
||||
# Zet de waarde van default_tenant_make_id
|
||||
if tenant.default_tenant_make_id:
|
||||
form.default_tenant_make_id.data = str(tenant.default_tenant_make_id)
|
||||
|
||||
# Haal de naam van de default make op als deze bestaat
|
||||
default_make_name = None
|
||||
if tenant.default_tenant_make:
|
||||
default_make_name = tenant.default_tenant_make.name
|
||||
|
||||
return render_template('user/tenant_overview.html', form=form, default_make_name=default_make_name)
|
||||
|
||||
|
||||
@user_bp.route('/tenant_project', methods=['GET', 'POST'])
|
||||
@@ -627,15 +648,17 @@ def delete_tenant_project(tenant_project_id):
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def tenant_make():
|
||||
form = TenantMakeForm()
|
||||
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
|
||||
default_customisation_options = create_default_config_from_type_config(customisation_config["configuration"])
|
||||
form.add_dynamic_fields("configuration", customisation_config, default_customisation_options)
|
||||
|
||||
if form.validate_on_submit():
|
||||
tenant_id = session['tenant']['id']
|
||||
new_tenant_make = TenantMake()
|
||||
form.populate_obj(new_tenant_make)
|
||||
new_tenant_make.tenant_id = tenant_id
|
||||
customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
|
||||
new_tenant_make.chat_customisation_options = create_default_config_from_type_config(
|
||||
customisation_config["configuration"])
|
||||
form.add_dynamic_fields("configuration", customisation_config, new_tenant_make.chat_customisation_options)
|
||||
customisation_options = form.get_dynamic_data("configuration")
|
||||
new_tenant_make.chat_customisation_options = json.dumps(customisation_options)
|
||||
set_logging_information(new_tenant_make, dt.now(tz.utc))
|
||||
|
||||
try:
|
||||
@@ -724,6 +747,23 @@ def handle_tenant_make_selection():
|
||||
|
||||
if action == 'edit_tenant_make':
|
||||
return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=tenant_make_id))
|
||||
elif action == 'set_as_default':
|
||||
# Set this make as the default for the tenant
|
||||
tenant_id = session['tenant']['id']
|
||||
tenant = Tenant.query.get(tenant_id)
|
||||
tenant.default_tenant_make_id = tenant_make_id
|
||||
try:
|
||||
db.session.commit()
|
||||
flash(f'Default tenant make updated successfully.', 'success')
|
||||
# Update session data if necessary
|
||||
if 'tenant' in session:
|
||||
session['tenant'] = tenant.to_dict()
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to update default tenant make. Error: {str(e)}', 'danger')
|
||||
current_app.logger.error(f'Failed to update default tenant make. Error: {str(e)}')
|
||||
|
||||
return redirect(prefixed_url_for('user_bp.tenant_makes'))
|
||||
|
||||
def reset_uniquifier(user):
|
||||
security.datastore.set_uniquifier(user)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import asyncio
|
||||
import json
|
||||
from os import wait
|
||||
from typing import Optional, List
|
||||
|
||||
from crewai.flow.flow import start, listen, and_
|
||||
from flask import current_app
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.extensions import db
|
||||
from common.models.user import Tenant
|
||||
from common.models.interaction import Specialist
|
||||
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
|
||||
from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
|
||||
from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
|
||||
from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
|
||||
from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
|
||||
from common.services.interaction.specialist_services import SpecialistServices
|
||||
|
||||
|
||||
class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
||||
"""
|
||||
type: TRAICIE_ROLE_DEFINITION_SPECIALIST
|
||||
type_version: 1.0
|
||||
Traicie Role Definition Specialist Executor class
|
||||
"""
|
||||
|
||||
def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
|
||||
self.role_definition_crew = None
|
||||
|
||||
super().__init__(tenant_id, specialist_id, session_id, task_id)
|
||||
|
||||
# Load the Tenant & set language
|
||||
self.tenant = Tenant.query.get_or_404(tenant_id)
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return "TRAICIE_ROLE_DEFINITION_SPECIALIST"
|
||||
|
||||
@property
|
||||
def type_version(self) -> str:
|
||||
return "1.3"
|
||||
|
||||
def _config_task_agents(self):
|
||||
self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")
|
||||
|
||||
def _config_pydantic_outputs(self):
|
||||
self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")
|
||||
|
||||
def _instantiate_specialist(self):
|
||||
verbose = self.tuning
|
||||
|
||||
role_definition_agents = [self.traicie_hr_bp_agent]
|
||||
role_definition_tasks = [self.traicie_get_competencies_task]
|
||||
self.role_definition_crew = EveAICrewAICrew(
|
||||
self,
|
||||
"Role Definition Crew",
|
||||
agents=role_definition_agents,
|
||||
tasks=role_definition_tasks,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
self.flow = RoleDefinitionFlow(
|
||||
self,
|
||||
self.role_definition_crew
|
||||
)
|
||||
|
||||
def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
|
||||
self.log_tuning("Traicie Role Definition Specialist execution started", {})
|
||||
|
||||
flow_inputs = {
|
||||
"vacancy_text": arguments.vacancy_text,
|
||||
"role_name": arguments.role_name,
|
||||
'role_reference': arguments.role_reference,
|
||||
}
|
||||
|
||||
flow_results = self.flow.kickoff(inputs=flow_inputs)
|
||||
|
||||
flow_state = self.flow.state
|
||||
|
||||
results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
|
||||
if flow_state.competencies:
|
||||
results.competencies = flow_state.competencies
|
||||
|
||||
self.create_selection_specialist(arguments, flow_state.competencies)
|
||||
|
||||
self.log_tuning(f"Traicie Role Definition Specialist execution ended", {"Results": results.model_dump()})
|
||||
|
||||
return results
|
||||
|
||||
def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
|
||||
"""This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
|
||||
current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
|
||||
selection_comptencies = []
|
||||
for competency in competencies:
|
||||
selection_competency = {
|
||||
"title": competency.title,
|
||||
"description": competency.description,
|
||||
"assess": True,
|
||||
"is_knockout": False,
|
||||
}
|
||||
selection_comptencies.append(selection_competency)
|
||||
|
||||
selection_config = {
|
||||
"name": arguments.specialist_name,
|
||||
"competencies": selection_comptencies,
|
||||
"tone_of_voice": "Professional & Neutral",
|
||||
"language_level": "Standard",
|
||||
"role_reference": arguments.role_reference,
|
||||
"make": arguments.make,
|
||||
}
|
||||
name = arguments.role_name
|
||||
if len(name) > 50:
|
||||
name = name[:47] + "..."
|
||||
|
||||
new_specialist = Specialist(
|
||||
name=name,
|
||||
description=f"Specialist for {arguments.role_name} role",
|
||||
type="TRAICIE_SELECTION_SPECIALIST",
|
||||
type_version="1.1",
|
||||
tuning=False,
|
||||
configuration=selection_config,
|
||||
)
|
||||
try:
|
||||
db.session.add(new_specialist)
|
||||
db.session.commit()
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"Error creating selection specialist: {str(e)}")
|
||||
raise e
|
||||
|
||||
SpecialistServices.initialize_specialist(new_specialist.id, "TRAICIE_SELECTION_SPECIALIST", "1.0")
|
||||
|
||||
|
||||
|
||||
|
||||
class RoleDefinitionSpecialistInput(BaseModel):
|
||||
role_name: str = Field(..., alias="role_name")
|
||||
role_reference: Optional[str] = Field(..., alias="role_reference")
|
||||
vacancy_text: Optional[str] = Field(None, alias="vacancy_text")
|
||||
|
||||
|
||||
class RoleDefinitionSpecialistResult(SpecialistResult):
|
||||
competencies: Optional[List[ListItem]] = None
|
||||
|
||||
|
||||
class RoleDefFlowState(EveAIFlowState):
|
||||
"""Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
|
||||
input: Optional[RoleDefinitionSpecialistInput] = None
|
||||
competencies: Optional[List[ListItem]] = None
|
||||
|
||||
|
||||
class RoleDefinitionFlow(EveAICrewAIFlow[RoleDefFlowState]):
|
||||
def __init__(self,
|
||||
specialist_executor: CrewAIBaseSpecialistExecutor,
|
||||
role_definitiion_crew: EveAICrewAICrew,
|
||||
**kwargs):
|
||||
super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
|
||||
self.specialist_executor = specialist_executor
|
||||
self.role_definition_crew = role_definitiion_crew
|
||||
self.exception_raised = False
|
||||
|
||||
@start()
|
||||
def process_inputs(self):
|
||||
return ""
|
||||
|
||||
@listen(process_inputs)
|
||||
async def execute_role_definition (self):
|
||||
inputs = self.state.input.model_dump()
|
||||
try:
|
||||
current_app.logger.debug("In execute_role_definition")
|
||||
crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
|
||||
# Unfortunately, crew_output will only contain the output of the latest task.
|
||||
# As we will only take into account the flow state, we need to ensure both competencies and criteria
|
||||
# are copies to the flow state.
|
||||
update = {}
|
||||
for task in self.role_definition_crew.tasks:
|
||||
current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
|
||||
if task.name == "traicie_get_competencies_task":
|
||||
# update["competencies"] = task.output.pydantic.competencies
|
||||
self.state.competencies = task.output.pydantic.competencies
|
||||
# crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
|
||||
current_app.logger.debug(f"State after execute_role_definition: {self.state}")
|
||||
current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
|
||||
return crew_output
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
|
||||
self.exception_raised = True
|
||||
raise e
|
||||
|
||||
async def kickoff_async(self, inputs=None):
|
||||
current_app.logger.debug(f"Async kickoff {self.name}")
|
||||
current_app.logger.debug(f"Inputs: {inputs}")
|
||||
self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
|
||||
current_app.logger.debug(f"State: {self.state}")
|
||||
result = await super().kickoff_async(inputs)
|
||||
return self.state
|
||||
@@ -0,0 +1,197 @@
|
||||
import asyncio
|
||||
import json
|
||||
from os import wait
|
||||
from typing import Optional, List
|
||||
|
||||
from crewai.flow.flow import start, listen, and_
|
||||
from flask import current_app
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.extensions import db
|
||||
from common.models.user import Tenant
|
||||
from common.models.interaction import Specialist
|
||||
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
|
||||
from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
|
||||
from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
|
||||
from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
|
||||
from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
|
||||
from common.services.interaction.specialist_services import SpecialistServices
|
||||
|
||||
|
||||
class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
||||
"""
|
||||
type: TRAICIE_SELECTION_SPECIALIST
|
||||
type_version: 1.0
|
||||
Traicie Selection Specialist Executor class
|
||||
"""
|
||||
|
||||
def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
|
||||
self.role_definition_crew = None
|
||||
|
||||
super().__init__(tenant_id, specialist_id, session_id, task_id)
|
||||
|
||||
# Load the Tenant & set language
|
||||
self.tenant = Tenant.query.get_or_404(tenant_id)
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return "TRAICIE_SELECTION_SPECIALIST"
|
||||
|
||||
@property
|
||||
def type_version(self) -> str:
|
||||
return "1.0"
|
||||
|
||||
def _config_task_agents(self):
|
||||
self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")
|
||||
|
||||
def _config_pydantic_outputs(self):
|
||||
self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")
|
||||
|
||||
def _instantiate_specialist(self):
|
||||
verbose = self.tuning
|
||||
|
||||
role_definition_agents = [self.traicie_hr_bp_agent]
|
||||
role_definition_tasks = [self.traicie_get_competencies_task]
|
||||
self.role_definition_crew = EveAICrewAICrew(
|
||||
self,
|
||||
"Role Definition Crew",
|
||||
agents=role_definition_agents,
|
||||
tasks=role_definition_tasks,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
self.flow = RoleDefinitionFlow(
|
||||
self,
|
||||
self.role_definition_crew
|
||||
)
|
||||
|
||||
def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
|
||||
self.log_tuning("Traicie Role Definition Specialist execution started", {})
|
||||
|
||||
flow_inputs = {
|
||||
"vacancy_text": arguments.vacancy_text,
|
||||
"role_name": arguments.role_name,
|
||||
'role_reference': arguments.role_reference,
|
||||
}
|
||||
|
||||
flow_results = self.flow.kickoff(inputs=flow_inputs)
|
||||
|
||||
flow_state = self.flow.state
|
||||
|
||||
results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
|
||||
if flow_state.competencies:
|
||||
results.competencies = flow_state.competencies
|
||||
|
||||
self.create_selection_specialist(arguments, flow_state.competencies)
|
||||
|
||||
self.log_tuning(f"Traicie Role Definition Specialist execution ended", {"Results": results.model_dump()})
|
||||
|
||||
return results
|
||||
|
||||
def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
|
||||
"""This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
|
||||
current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
|
||||
selection_comptencies = []
|
||||
for competency in competencies:
|
||||
selection_competency = {
|
||||
"title": competency.title,
|
||||
"description": competency.description,
|
||||
"assess": True,
|
||||
"is_knockout": False,
|
||||
}
|
||||
selection_comptencies.append(selection_competency)
|
||||
|
||||
selection_config = {
|
||||
"name": arguments.specialist_name,
|
||||
"competencies": selection_comptencies,
|
||||
"tone_of_voice": "Professional & Neutral",
|
||||
"language_level": "Standard",
|
||||
"role_reference": arguments.role_reference,
|
||||
}
|
||||
name = arguments.role_name
|
||||
if len(name) > 50:
|
||||
name = name[:47] + "..."
|
||||
|
||||
new_specialist = Specialist(
|
||||
name=name,
|
||||
description=f"Specialist for {arguments.role_name} role",
|
||||
type="TRAICIE_SELECTION_SPECIALIST",
|
||||
type_version="1.0",
|
||||
tuning=False,
|
||||
configuration=selection_config,
|
||||
)
|
||||
try:
|
||||
db.session.add(new_specialist)
|
||||
db.session.commit()
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"Error creating selection specialist: {str(e)}")
|
||||
raise e
|
||||
|
||||
SpecialistServices.initialize_specialist(new_specialist.id, "TRAICIE_SELECTION_SPECIALIST", "1.0")
|
||||
|
||||
|
||||
|
||||
|
||||
class RoleDefinitionSpecialistInput(BaseModel):
|
||||
role_name: str = Field(..., alias="role_name")
|
||||
role_reference: Optional[str] = Field(..., alias="role_reference")
|
||||
vacancy_text: Optional[str] = Field(None, alias="vacancy_text")
|
||||
|
||||
|
||||
class RoleDefinitionSpecialistResult(SpecialistResult):
|
||||
competencies: Optional[List[ListItem]] = None
|
||||
|
||||
|
||||
class RoleDefFlowState(EveAIFlowState):
|
||||
"""Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
|
||||
input: Optional[RoleDefinitionSpecialistInput] = None
|
||||
competencies: Optional[List[ListItem]] = None
|
||||
|
||||
|
||||
class RoleDefinitionFlow(EveAICrewAIFlow[RoleDefFlowState]):
|
||||
def __init__(self,
|
||||
specialist_executor: CrewAIBaseSpecialistExecutor,
|
||||
role_definitiion_crew: EveAICrewAICrew,
|
||||
**kwargs):
|
||||
super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
|
||||
self.specialist_executor = specialist_executor
|
||||
self.role_definition_crew = role_definitiion_crew
|
||||
self.exception_raised = False
|
||||
|
||||
@start()
|
||||
def process_inputs(self):
|
||||
return ""
|
||||
|
||||
@listen(process_inputs)
|
||||
async def execute_role_definition (self):
|
||||
inputs = self.state.input.model_dump()
|
||||
try:
|
||||
current_app.logger.debug("In execute_role_definition")
|
||||
crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
|
||||
# Unfortunately, crew_output will only contain the output of the latest task.
|
||||
# As we will only take into account the flow state, we need to ensure both competencies and criteria
|
||||
# are copies to the flow state.
|
||||
update = {}
|
||||
for task in self.role_definition_crew.tasks:
|
||||
current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
|
||||
if task.name == "traicie_get_competencies_task":
|
||||
# update["competencies"] = task.output.pydantic.competencies
|
||||
self.state.competencies = task.output.pydantic.competencies
|
||||
# crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
|
||||
current_app.logger.debug(f"State after execute_role_definition: {self.state}")
|
||||
current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
|
||||
return crew_output
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
|
||||
self.exception_raised = True
|
||||
raise e
|
||||
|
||||
async def kickoff_async(self, inputs=None):
|
||||
current_app.logger.debug(f"Async kickoff {self.name}")
|
||||
current_app.logger.debug(f"Inputs: {inputs}")
|
||||
self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
|
||||
current_app.logger.debug(f"State: {self.state}")
|
||||
result = await super().kickoff_async(inputs)
|
||||
return self.state
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Add default TenantMake to Tenant model
|
||||
|
||||
Revision ID: 83d4e90f87c6
|
||||
Revises: f40d16a0965a
|
||||
Create Date: 2025-06-09 15:42:51.503696
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '83d4e90f87c6'
|
||||
down_revision = 'f40d16a0965a'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
with op.batch_alter_table('tenant', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('default_tenant_make_id', sa.Integer(), nullable=True))
|
||||
batch_op.create_foreign_key(None, 'tenant_make', ['default_tenant_make_id'], ['id'], referent_schema='public')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tenant', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_column('default_tenant_make_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Make TenantMake name unique
|
||||
|
||||
Revision ID: f40d16a0965a
|
||||
Revises: 200bda7f5251
|
||||
Create Date: 2025-06-09 06:15:56.791634
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f40d16a0965a'
|
||||
down_revision = '200bda7f5251'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tenant_make', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint(None, ['name'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tenant_make', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Add tenant_make reference to SpecialistMagicLink
|
||||
|
||||
Revision ID: 2b6ae6cc923e
|
||||
Revises: a179785e5362
|
||||
Create Date: 2025-06-09 15:59:39.157066
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import pgvector
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2b6ae6cc923e'
|
||||
down_revision = 'a179785e5362'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('specialist_magic_link', sa.Column('tenant_make_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(None, 'specialist_magic_link', 'tenant_make', ['tenant_make_id'], ['id'], referent_schema='public', ondelete='CASCADE')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Add active field to Specialist
|
||||
|
||||
Revision ID: a179785e5362
|
||||
Revises: c71facc0ce7e
|
||||
Create Date: 2025-06-09 08:30:18.532600
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import pgvector
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a179785e5362'
|
||||
down_revision = 'c71facc0ce7e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('specialist', sa.Column('active', sa.Boolean(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('specialist', 'active')
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user