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 ###