From ba523a95c505bc9534a24fae90715099c0a025a5 Mon Sep 17 00:00:00 2001 From: Josako Date: Fri, 25 Jul 2025 04:27:19 +0200 Subject: [PATCH] - RQC output of TRAICIE_SELECTION_SPECIALIST to EveAIDataCapsule --- common/models/interaction.py | 18 +++ .../services/interaction/capsule_services.py | 25 ++++ common/utils/cache/config_cache.py | 11 +- .../traicie/TRAICIE_RQC/1.0.0.yaml | 8 ++ config/type_defs/capsule_types.py | 8 ++ .../interaction/view_data_capsule.html | 111 ++++++++++++++++++ eveai_app/templates/navbar.html | 1 + eveai_app/views/interaction_views.py | 49 +++++++- .../list_views/interaction_list_views.py | 50 +++++++- .../TRAICIE_SELECTION_SPECIALIST/1_4.py | 17 ++- ..._introduction_of_eveaidatacapsule_model.py | 45 +++++++ 11 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 common/services/interaction/capsule_services.py create mode 100644 config/data_capsules/traicie/TRAICIE_RQC/1.0.0.yaml create mode 100644 config/type_defs/capsule_types.py create mode 100644 eveai_app/templates/interaction/view_data_capsule.html create mode 100644 migrations/tenant/versions/5e3dd539e5c1_introduction_of_eveaidatacapsule_model.py diff --git a/common/models/interaction.py b/common/models/interaction.py index 24024b4..d429ce9 100644 --- a/common/models/interaction.py +++ b/common/models/interaction.py @@ -94,6 +94,24 @@ class EveAIAsset(db.Model): last_used_at = db.Column(db.DateTime, nullable=True) +class EveAIDataCapsule(db.Model): + id = db.Column(db.Integer, primary_key=True) + chat_session_id = db.Column(db.Integer, db.ForeignKey(ChatSession.id), nullable=False) + type = db.Column(db.String(50), nullable=False, default="STANDARD_RAG") + type_version = db.Column(db.String(20), nullable=True, default="1.0.0") + configuration = db.Column(JSONB, nullable=True) + data = db.Column(JSONB, nullable=True) + + # Versioning Information + created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now()) + created_by = db.Column(db.Integer, db.ForeignKey(User.id), nullable=True) + 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)) + + # Unieke constraint voor chat_session_id, type en type_version + __table_args__ = (db.UniqueConstraint('chat_session_id', 'type', 'type_version', name='uix_data_capsule_session_type_version'),) + + class EveAIAgent(db.Model): id = db.Column(db.Integer, primary_key=True) specialist_id = db.Column(db.Integer, db.ForeignKey(Specialist.id), nullable=False) diff --git a/common/services/interaction/capsule_services.py b/common/services/interaction/capsule_services.py new file mode 100644 index 0000000..f7a9689 --- /dev/null +++ b/common/services/interaction/capsule_services.py @@ -0,0 +1,25 @@ +from datetime import datetime as dt, timezone as tz + +from common.models.interaction import EveAIDataCapsule +from common.extensions import db +from common.utils.model_logging_utils import set_logging_information, update_logging_information + + +class CapsuleServices: + @staticmethod + def push_capsule_data(chat_session_id: str, type: str, type_version: str, configuration: dict, data: dict): + capsule = EveAIDataCapsule.query.filter_by(chat_session_id=chat_session_id, type=type, type_version=type_version).first() + if capsule: + # Update bestaande capsule als deze al bestaat + capsule.configuration = configuration + capsule.data = data + update_logging_information(capsule, dt.now(tz.utc)) + else: + # Maak nieuwe capsule aan als deze nog niet bestaat + capsule = EveAIDataCapsule(chat_session_id=chat_session_id, type=type, type_version=type_version, + configuration=configuration, data=data) + set_logging_information(capsule, dt.now(tz.utc)) + db.session.add(capsule) + + db.session.commit() + return capsule \ No newline at end of file diff --git a/common/utils/cache/config_cache.py b/common/utils/cache/config_cache.py index 2249b58..05649e5 100644 --- a/common/utils/cache/config_cache.py +++ b/common/utils/cache/config_cache.py @@ -7,7 +7,7 @@ from flask import current_app from common.utils.cache.base import CacheHandler, CacheKey from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \ - catalog_types, partner_service_types, processor_types, customisation_types, specialist_form_types + catalog_types, partner_service_types, processor_types, customisation_types, specialist_form_types, capsule_types def is_major_minor(version: str) -> bool: @@ -485,6 +485,15 @@ SpecialistFormConfigCacheHandler, SpecialistFormConfigVersionTreeCacheHandler, S ) +CapsuleConfigCacheHandler, CapsuleConfigVersionTreeCacheHandler, CapsuleConfigTypesCacheHandler = ( + create_config_cache_handlers( + config_type='data_capsules', + config_dir='config/data_capsules', + types_module=capsule_types.CAPSULE_TYPES + ) +) + + def register_config_cache_handlers(cache_manager) -> None: cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config') cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config') diff --git a/config/data_capsules/traicie/TRAICIE_RQC/1.0.0.yaml b/config/data_capsules/traicie/TRAICIE_RQC/1.0.0.yaml new file mode 100644 index 0000000..7c976e6 --- /dev/null +++ b/config/data_capsules/traicie/TRAICIE_RQC/1.0.0.yaml @@ -0,0 +1,8 @@ +version: "1.0.0" +name: "RQC" +description: "Recruitment Qualified Candidate" +configuration: {} +metadata: + author: "Josako" + date_added: "2025-07-24" + description: "Capsule storing RQC information" diff --git a/config/type_defs/capsule_types.py b/config/type_defs/capsule_types.py new file mode 100644 index 0000000..febe98c --- /dev/null +++ b/config/type_defs/capsule_types.py @@ -0,0 +1,8 @@ +# Catalog Types +CAPSULE_TYPES = { + "TRAICIE_RQC": { + "name": "Traicie Recruitment Qualified Candidate Capsule", + "description": "A capsule storing RQCs", + "partner": "traicie" + }, +} diff --git a/eveai_app/templates/interaction/view_data_capsule.html b/eveai_app/templates/interaction/view_data_capsule.html new file mode 100644 index 0000000..d4bc48d --- /dev/null +++ b/eveai_app/templates/interaction/view_data_capsule.html @@ -0,0 +1,111 @@ +{% extends "base.html" %} + +{% block title %}View Data Capsule{% endblock %} + +{% block content_title %}Data Capsule Details{% endblock %} +{% block content_description %}View information about this EvAI Data Capsule and its content.{% endblock %} + +{% block content %} +
+ +

Basis Informatie

+ + + + + + + + + + + + + + + + + + + + + +
ID{{ data_capsule.id }}
Type{{ data_capsule.type }}
Type Version{{ data_capsule.type_version }}
Created At{{ data_capsule.created_at }}
Chat Session ID + + {{ data_capsule.chat_session_id }} + +
+ + +

Chat Sessie Informatie

+ + + + + + + + + + + + + +
Session ID{{ chat_session.session_id }}
Session Start{{ chat_session.session_start }}
Session End{{ chat_session.session_end }}
+ + + {% if data_capsule.configuration %} +

Configuratie

+
+
{{ data_capsule.configuration | tojson(indent=2) }}
+ {% endif %} + + + {% if data_capsule.data %} +

Data

+
+
{{ data_capsule.data | tojson(indent=2) }}
+ {% endif %} + + + +
+ + + +{% endblock %} diff --git a/eveai_app/templates/navbar.html b/eveai_app/templates/navbar.html index 443ef92..4360bcd 100644 --- a/eveai_app/templates/navbar.html +++ b/eveai_app/templates/navbar.html @@ -109,6 +109,7 @@ {'name': 'Specialists', 'url': '/interaction/specialists', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']}, {'name': 'Specialist Magic Links', 'url': '/interaction/specialist_magic_links', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']}, {'name': 'Assets', 'url': '/interaction/assets', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']}, + {'name': 'Data Capsules', 'url': '/interaction//eveai_data_capsules', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']}, {'name': 'Chat Sessions', 'url': '/interaction/chat_sessions', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']}, ]) }} {% endif %} diff --git a/eveai_app/views/interaction_views.py b/eveai_app/views/interaction_views.py index c956256..cb0a60c 100644 --- a/eveai_app/views/interaction_views.py +++ b/eveai_app/views/interaction_views.py @@ -15,7 +15,7 @@ from werkzeug.utils import secure_filename from common.models.document import Embedding, DocumentVersion, Retriever from common.models.interaction import (ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever, - EveAIAgent, EveAITask, EveAITool, EveAIAsset, SpecialistMagicLink) + EveAIAgent, EveAITask, EveAITool, EveAIAsset, SpecialistMagicLink, EveAIDataCapsule) from common.extensions import db, cache_manager from common.models.user import SpecialistMagicLinkTenant @@ -32,7 +32,8 @@ from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAge SpecialistMagicLinkForm, EditSpecialistMagicLinkForm) from eveai_app.views.list_views.interaction_list_views import (get_specialists_list_view, get_assets_list_view, - get_magic_links_list_view, get_chat_sessions_list_view) + get_magic_links_list_view, get_chat_sessions_list_view, + get_eveai_data_capsules_list_view) from eveai_app.views.list_views.list_view_utils import render_list_view interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction') @@ -747,12 +748,42 @@ def handle_specialist_magic_link_selection(): # Routes for Asset Management --------------------------------------------------------------------- +@interaction_bp.route('/eveai_data_capsules', methods=['GET', 'POST']) +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def eveai_data_capsules(): + # Get configuration and render the list view + config = get_eveai_data_capsules_list_view() + return render_list_view('list_view.html', **config) + + @interaction_bp.route('/assets', methods=['GET', 'POST']) def assets(): config = get_assets_list_view() return render_list_view('list_view.html', **config) +@interaction_bp.route('/handle_data_capsule_selection', methods=['POST']) +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def handle_data_capsule_selection(): + data_capsule_identification = request.form['selected_row'] + capsule_id = ast.literal_eval(data_capsule_identification).get('value') + action = request.form['action'] + + # Get the data capsule + data_capsule = EveAIDataCapsule.query.get_or_404(capsule_id) + + match action: + case 'view_data_capsule': + # For now, we'll just redirect to view_data_capsule + return redirect(prefixed_url_for('interaction_bp.view_data_capsule', data_capsule_id=capsule_id)) + case 'view_chat_session': + # Redirect to the chat session + return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id)) + + # Default redirect back to the data capsules list + return redirect(prefixed_url_for('interaction_bp.eveai_data_capsules')) + + @interaction_bp.route('/handle_asset_selection', methods=['POST']) def handle_asset_selection(): action = request.form.get('action') @@ -765,6 +796,20 @@ def handle_asset_selection(): return redirect(prefixed_url_for('interaction_bp.assets')) +@interaction_bp.route('/view_data_capsule/', methods=['GET']) +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def view_data_capsule(data_capsule_id): + # Get the data capsule + data_capsule = EveAIDataCapsule.query.get_or_404(data_capsule_id) + + # Get the related chat session + chat_session = ChatSession.query.get_or_404(data_capsule.chat_session_id) + + return render_template('interaction/view_data_capsule.html', + data_capsule=data_capsule, + chat_session=chat_session) + + @interaction_bp.route('/edit_asset/', methods=['GET', 'POST']) def edit_asset(asset_id): asset = EveAIAsset.query.get_or_404(asset_id) diff --git a/eveai_app/views/list_views/interaction_list_views.py b/eveai_app/views/list_views/interaction_list_views.py index 8acdb94..0a69428 100644 --- a/eveai_app/views/list_views/interaction_list_views.py +++ b/eveai_app/views/list_views/interaction_list_views.py @@ -4,7 +4,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy import desc import ast -from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession +from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession, EveAIDataCapsule from common.utils.nginx_utils import prefixed_url_for from eveai_app.views.list_views.list_view_utils import render_list_view @@ -196,3 +196,51 @@ def get_chat_sessions_list_view(): 'table_height': 800 } + +def get_eveai_data_capsules_list_view(): + """Generate the EvAI Data Capsules list view configuration""" + # Get all data capsules ordered by creation date (descending) + data_capsules_query = EveAIDataCapsule.query.order_by(desc(EveAIDataCapsule.created_at)) + all_data_capsules = data_capsules_query.all() + + # Prepare data for Tabulator + data = [] + for capsule in all_data_capsules: + data.append({ + 'id': capsule.id, + 'chat_session_id': capsule.chat_session_id, + 'type': capsule.type, + 'type_version': capsule.type_version, + 'created_at': capsule.created_at.strftime('%Y-%m-%d %H:%M:%S') if capsule.created_at else '' + }) + + # Column definitions + columns = [ + {'title': 'ID', 'field': 'id', 'width': 80}, + {'title': 'Chat Session ID', 'field': 'chat_session_id'}, + {'title': 'Type', 'field': 'type'}, + {'title': 'Type Version', 'field': 'type_version'}, + {'title': 'Created At', 'field': 'created_at'} + ] + + # Action definitions + actions = [ + {'value': 'view_data_capsule', 'text': 'View Details', 'class': 'btn-primary', 'requiresSelection': True}, + {'value': 'view_chat_session', 'text': 'View Chat Session', 'class': 'btn-secondary', 'requiresSelection': True} + ] + + # Initial sort configuration + initial_sort = [{'column': 'created_at', 'dir': 'desc'}] + + return { + 'title': 'EvAI Data Capsules', + 'data': data, + 'columns': columns, + 'actions': actions, + 'initial_sort': initial_sort, + 'table_id': 'eveai_data_capsules_table', + 'form_action': url_for('interaction_bp.handle_data_capsule_selection'), + 'description': 'View all EvAI Data Capsules', + 'table_height': 800 + } + diff --git a/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_4.py b/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_4.py index 90ab38d..e081a60 100644 --- a/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_4.py +++ b/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_4.py @@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, EmailStr from common.extensions import cache_manager, db, minio_client from common.models.interaction import EveAIAsset from common.models.user import Tenant +from common.services.interaction.capsule_services import CapsuleServices from common.services.utils.human_answer_services import HumanAnswerServices from common.services.utils.translation_services import TranslationServices from common.utils.business_event_context import current_event @@ -308,6 +309,13 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor): self.flow.state.personal_contact_data = arguments.form_values self.flow.state.form_request = time_pref_form + rqc_info = { + "ko_criteria_answers": self.flow.state.ko_criteria_answers, + "personal_contact_data": self.flow.state.personal_contact_data, + } + + CapsuleServices.push_capsule_data(self._cached_session.id, "TRAICIE_RQC", "1.0", {}, rqc_info) + results = SelectionResult.create_for_type(self.type, self.type_version,) return results @@ -327,8 +335,15 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor): current_app.logger.debug(f"Contact time evaluation: {arguments.form_values}") self.flow.state.contact_time_prefs = arguments.form_values + rqc_info = { + "ko_criteria_answers": self.flow.state.ko_criteria_answers, + "personal_contact_data": self.flow.state.personal_contact_data, + "contact_time_prefs": self.flow.state.contact_time_prefs, + } + + CapsuleServices.push_capsule_data(self._cached_session.id, "TRAICIE_RQC", "1.0", {}, rqc_info) + results = SelectionResult.create_for_type(self.type, self.type_version,) - current_app.logger.debug(f"Results: {results.model_dump()}") return results diff --git a/migrations/tenant/versions/5e3dd539e5c1_introduction_of_eveaidatacapsule_model.py b/migrations/tenant/versions/5e3dd539e5c1_introduction_of_eveaidatacapsule_model.py new file mode 100644 index 0000000..d34c8bd --- /dev/null +++ b/migrations/tenant/versions/5e3dd539e5c1_introduction_of_eveaidatacapsule_model.py @@ -0,0 +1,45 @@ +"""Introduction of EveAIDataCapsule model + +Revision ID: 5e3dd539e5c1 +Revises: 26e8f0d8c143 +Create Date: 2025-07-24 13:31:46.238128 + +""" +from alembic import op +import sqlalchemy as sa +import pgvector +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '5e3dd539e5c1' +down_revision = '26e8f0d8c143' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('eve_ai_data_capsule', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('chat_session_id', sa.Integer(), nullable=False), + sa.Column('type', sa.String(length=50), nullable=False), + sa.Column('type_version', sa.String(length=20), nullable=True), + sa.Column('configuration', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('created_by', sa.Integer(), nullable=True), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_by', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['chat_session_id'], ['chat_session.id'], ), + sa.ForeignKeyConstraint(['created_by'], ['public.user.id'], ), + sa.ForeignKeyConstraint(['updated_by'], ['public.user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('chat_session_id', 'type', 'type_version', name='uix_data_capsule_session_type_version') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('eve_ai_data_capsule') + # ### end Alembic commands ###