Chat client changes
- Form values shown correct in MessageHistory of Chat client - Improements to CSS - Move css en js to assets directory - Introduce better Personal Contact Form & Professional Contact Form - Start working on actual Selection Specialist
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
|
type: "PERSONAL_CONTACT_FORM"
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "Personal Contact Form"
|
name: "Personal Contact Form"
|
||||||
icon: "call"
|
icon: "person"
|
||||||
fields:
|
fields:
|
||||||
name:
|
name:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
@@ -17,24 +18,28 @@ fields:
|
|||||||
type: "str"
|
type: "str"
|
||||||
description: "Your Phone Number"
|
description: "Your Phone Number"
|
||||||
required: true
|
required: true
|
||||||
Address:
|
address:
|
||||||
name: "Address"
|
name: "Address"
|
||||||
type: "text"
|
type: "string"
|
||||||
description: "Your Address"
|
description: "Your Address"
|
||||||
required: false
|
required: false
|
||||||
status:
|
zip:
|
||||||
name: "Marital Status"
|
name: "Postal Code"
|
||||||
type: "enum"
|
type: "string"
|
||||||
description: "Your Marital Status"
|
description: "Postal Code"
|
||||||
required: false
|
required: false
|
||||||
default: "single"
|
city:
|
||||||
allowed_values:
|
name: "City"
|
||||||
- "single"
|
type: "string"
|
||||||
- "married"
|
description: "City"
|
||||||
- "divorced"
|
required: false
|
||||||
can_contact:
|
country:
|
||||||
name: "Allow Contact"
|
name: "Country"
|
||||||
|
type: "string"
|
||||||
|
description: "Country"
|
||||||
|
required: false
|
||||||
|
consent:
|
||||||
|
name: "Consent"
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
description: "Allow us to contact you?"
|
description: "Consent"
|
||||||
required: true
|
required: true
|
||||||
default: false
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
type: "PROFESSIONAL_CONTACT_FORM"
|
||||||
|
version: "1.0.0"
|
||||||
|
name: "Professional Contact Form"
|
||||||
|
icon: "account_circle"
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
name: "Name"
|
||||||
|
description: "Your name"
|
||||||
|
type: "str"
|
||||||
|
required: true
|
||||||
|
email:
|
||||||
|
name: "Email"
|
||||||
|
type: "str"
|
||||||
|
description: "Your Name"
|
||||||
|
required: true
|
||||||
|
phone:
|
||||||
|
name: "Phone Number"
|
||||||
|
type: "str"
|
||||||
|
description: "Your Phone Number"
|
||||||
|
required: true
|
||||||
|
company:
|
||||||
|
name: "Company Name"
|
||||||
|
type: "str"
|
||||||
|
description: "Company Name"
|
||||||
|
required: true
|
||||||
|
job_title:
|
||||||
|
name: "Job Title"
|
||||||
|
type: "str"
|
||||||
|
description: "Job Title"
|
||||||
|
required: false
|
||||||
|
address:
|
||||||
|
name: "Address"
|
||||||
|
type: "str"
|
||||||
|
description: "Your Address"
|
||||||
|
required: false
|
||||||
|
zip:
|
||||||
|
name: "Postal Code"
|
||||||
|
type: "str"
|
||||||
|
description: "Postal Code"
|
||||||
|
required: false
|
||||||
|
city:
|
||||||
|
name: "City"
|
||||||
|
type: "str"
|
||||||
|
description: "City"
|
||||||
|
required: false
|
||||||
|
country:
|
||||||
|
name: "Country"
|
||||||
|
type: "str"
|
||||||
|
description: "Country"
|
||||||
|
required: false
|
||||||
|
consent:
|
||||||
|
name: "Consent"
|
||||||
|
type: "bool"
|
||||||
|
description: "Consent"
|
||||||
|
required: true
|
||||||
@@ -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"
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
# Specialist Form Types
|
# Specialist Form Types
|
||||||
SPECIALIST_FORM_TYPES = {
|
SPECIALIST_FORM_TYPES = {
|
||||||
"PERSONAL_CONTACT_FORM": {
|
"PERSONAL_CONTACT_FORM": {
|
||||||
"name": "Contact Form",
|
"name": "Personal Contact Form",
|
||||||
"description": "A form for entering your personal contact details",
|
"description": "A form for entering your personal contact details",
|
||||||
},
|
},
|
||||||
|
"PROFESSIONAL_CONTACT_FORM": {
|
||||||
|
"name": "Professional Contact Form",
|
||||||
|
"description": "A form for entering your professional contact details",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
# Script to copy eveai_chat_client/static files to nginx/static
|
# Script to copy eveai_chat_client/static files to nginx/static
|
||||||
# without overwriting existing files
|
# without overwriting existing files
|
||||||
|
|
||||||
SRC_DIR="../eveai_chat_client/static"
|
SRC_DIR="../eveai_chat_client/static/assets"
|
||||||
DEST_DIR="../nginx/static/assets"
|
DEST_DIR="../nginx/static/assets"
|
||||||
|
|
||||||
# Check if source directory exists
|
# Check if source directory exists
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background: rgba(255,255,255,0.1);
|
background: rgba(255,255,255,0.1);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255,255,255,0.2);
|
|
||||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1000px; /* Optimale breedte */
|
max-width: 1000px; /* Optimale breedte */
|
||||||
@@ -377,7 +376,7 @@
|
|||||||
|
|
||||||
/* User message bubble styling */
|
/* User message bubble styling */
|
||||||
.message.user .message-content {
|
.message.user .message-content {
|
||||||
background: linear-gradient(135deg, #007bff, #0056b3);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
color: white;
|
color: white;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
}
|
}
|
||||||
@@ -385,9 +384,8 @@
|
|||||||
/* AI/Bot message bubble styling */
|
/* AI/Bot message bubble styling */
|
||||||
.message.ai .message-content,
|
.message.ai .message-content,
|
||||||
.message.bot .message-content {
|
.message.bot .message-content {
|
||||||
background: #f8f9fa;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: #212529;
|
color: #212529;
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
margin-right: 60px;
|
margin-right: 60px;
|
||||||
}
|
}
|
||||||
@@ -670,7 +668,6 @@
|
|||||||
/* Progress Tracker Styling */
|
/* Progress Tracker Styling */
|
||||||
.progress-tracker {
|
.progress-tracker {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -683,8 +680,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-tracker.completed {
|
.progress-tracker.completed {
|
||||||
border-color: #28a745;
|
background: rgba(155, 255, 155, 0.1);
|
||||||
background: #d4edda;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-header {
|
.progress-header {
|
||||||
120
eveai_chat_client/static/assets/css/chat-input.css
Normal file
120
eveai_chat_client/static/assets/css/chat-input.css
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/* ChatInput component styling */
|
||||||
|
|
||||||
|
/* Algemene container */
|
||||||
|
.chat-input-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input veld en knoppen */
|
||||||
|
.chat-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-main {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 10px 40px 10px 15px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 20px;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input:focus {
|
||||||
|
border-color: #0084ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input.over-limit {
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Character counter */
|
||||||
|
.character-counter {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-counter.over-limit {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input actions */
|
||||||
|
.input-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verzendknop */
|
||||||
|
.send-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #0084ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:hover {
|
||||||
|
background-color: #0077e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn.form-mode {
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn.form-mode:hover {
|
||||||
|
background-color: #43a047;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
|
.loading-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formulier in chat input */
|
||||||
|
.dynamic-form-container {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px 15px 5px 15px;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
161
eveai_chat_client/static/assets/css/chat-message.css
Normal file
161
eveai_chat_client/static/assets/css/chat-message.css
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/* chat-message.css */
|
||||||
|
|
||||||
|
/* Algemene styling voor berichten */
|
||||||
|
.message {
|
||||||
|
max-width: 90%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.user {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
width: 100%;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formulier styling */
|
||||||
|
.form-display {
|
||||||
|
margin: 15px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: rgba(245, 245, 245, 0.7);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabel styling voor formulieren */
|
||||||
|
.form-result-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-result-table th {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-result-table td {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-result-table td:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling voor formulier invoervelden */
|
||||||
|
.form-result-table input.form-input,
|
||||||
|
.form-result-table textarea.form-textarea,
|
||||||
|
.form-result-table select.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-result-table textarea.form-textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling voor tabel cellen */
|
||||||
|
.form-result-table .field-label {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 35%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-result-table .field-value {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch styling */
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-knob {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Material icon styling */
|
||||||
|
.material-symbols-outlined {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zorgt dat het lettertype consistent is */
|
||||||
|
.message-text {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form error styling */
|
||||||
|
.form-error {
|
||||||
|
color: red;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
91
eveai_chat_client/static/assets/css/form-message.css
Normal file
91
eveai_chat_client/static/assets/css/form-message.css
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/* Styling voor formulier in berichten */
|
||||||
|
.message .form-display {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: rgba(245, 245, 245, 0.7);
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.user .form-display {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai .form-display {
|
||||||
|
background-color: rgba(245, 245, 250, 0.7);
|
||||||
|
}
|
||||||
|
/* Styling voor formulieren in berichten */
|
||||||
|
|
||||||
|
.form-display {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.03);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form-values {
|
||||||
|
background-color: rgba(0, 123, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Speciale styling voor read-only formulieren in user messages */
|
||||||
|
.user-form .form-field {
|
||||||
|
margin-bottom: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form .field-label {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #555 !important;
|
||||||
|
padding: 2px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form .field-value {
|
||||||
|
padding: 2px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schakel hover effecten uit voor read-only formulieren */
|
||||||
|
.read-only .form-field:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtiele scheiding tussen velden */
|
||||||
|
.dynamic-form.read-only .form-fields {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verklein vorm titels in berichten */
|
||||||
|
.message-form .form-title {
|
||||||
|
font-size: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-form .form-description {
|
||||||
|
font-size: 0.85em !important;
|
||||||
|
}
|
||||||
|
.form-readonly {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-readonly .field-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-readonly .field-value {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-readonly .text-value {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Algemene styling verbetering voor berichten */
|
||||||
|
.message-text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
@@ -170,21 +170,22 @@ export const ChatApp = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Message management
|
// Message management
|
||||||
addMessage(content, sender, type = 'text', formData = null) {
|
addMessage(content, sender, type = 'text', formData = null, formValues = null) {
|
||||||
const message = {
|
const message = {
|
||||||
id: this.messageIdCounter++,
|
id: this.messageIdCounter++,
|
||||||
content,
|
content,
|
||||||
sender,
|
sender,
|
||||||
type,
|
type,
|
||||||
formData,
|
formData,
|
||||||
|
formValues,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
status: sender === 'user' ? 'sent' : 'delivered'
|
status: sender === 'user' ? 'sent' : 'delivered'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.allMessages.push(message);
|
this.allMessages.push(message);
|
||||||
|
|
||||||
// Initialize form values if it's a form
|
// Initialize form values if it's a form and no values were provided
|
||||||
if (type === 'form' && formData) {
|
if (type === 'form' && formData && !formValues) {
|
||||||
// Vue 3 compatibele manier om reactieve objecten bij te werken
|
// Vue 3 compatibele manier om reactieve objecten bij te werken
|
||||||
this.formValues[message.id] = {};
|
this.formValues[message.id] = {};
|
||||||
formData.fields.forEach(field => {
|
formData.fields.forEach(field => {
|
||||||
@@ -203,19 +204,32 @@ export const ChatApp = {
|
|||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Helper functie om formulierdata toe te voegen aan bestaande berichten
|
||||||
|
attachFormDataToMessage(messageId, formData, formValues) {
|
||||||
|
const message = this.allMessages.find(m => m.id === messageId);
|
||||||
|
if (message) {
|
||||||
|
message.formData = formData;
|
||||||
|
message.formValues = formValues;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
updateCurrentMessage(value) {
|
updateCurrentMessage(value) {
|
||||||
this.currentMessage = value;
|
this.currentMessage = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Message sending
|
// Message sending (alleen voor gewone tekstberichten, geen formulieren)
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
const text = this.currentMessage.trim();
|
const text = this.currentMessage.trim();
|
||||||
|
|
||||||
|
// Controleer of we kunnen verzenden
|
||||||
if (!text || this.isLoading) return;
|
if (!text || this.isLoading) return;
|
||||||
|
|
||||||
console.log('Sending message:', text);
|
console.log('Sending text message:', text);
|
||||||
|
|
||||||
// Add user message
|
// Add user message
|
||||||
const userMessage = this.addMessage(text, 'user', 'text');
|
const userMessage = this.addMessage(text, 'user', 'text');
|
||||||
|
|
||||||
|
// Wis input
|
||||||
this.currentMessage = '';
|
this.currentMessage = '';
|
||||||
|
|
||||||
// Show typing and loading state
|
// Show typing and loading state
|
||||||
@@ -223,11 +237,14 @@ export const ChatApp = {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.callAPI('/api/send_message', {
|
// Verzamel gegevens voor de API call
|
||||||
|
const apiData = {
|
||||||
message: text,
|
message: text,
|
||||||
conversation_id: this.conversationId,
|
conversation_id: this.conversationId,
|
||||||
user_id: this.userId
|
user_id: this.userId
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const response = await this.callAPI('/api/send_message', apiData);
|
||||||
|
|
||||||
// Hide typing indicator
|
// Hide typing indicator
|
||||||
this.isTyping = false;
|
this.isTyping = false;
|
||||||
@@ -278,35 +295,61 @@ export const ChatApp = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Submitting form from input:', this.currentInputFormData.title, formValues);
|
console.log('Form values received:', formValues);
|
||||||
|
console.log('Current input form data:', this.currentInputFormData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.callAPI('/api/submit_form', {
|
// Maak een user message met formuliergegevens én eventuele tekst
|
||||||
formData: formValues,
|
const userMessage = this.addMessage(
|
||||||
formType: this.currentInputFormData.title,
|
this.currentMessage.trim(), // Voeg tekst toe als die er is
|
||||||
|
'user',
|
||||||
|
'text'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Voeg formuliergegevens toe aan het bericht
|
||||||
|
userMessage.formData = this.currentInputFormData;
|
||||||
|
userMessage.formValues = formValues;
|
||||||
|
|
||||||
|
// Reset het tekstbericht
|
||||||
|
this.currentMessage = '';
|
||||||
|
this.$emit('update-message', '');
|
||||||
|
|
||||||
|
// Toon laad-indicator
|
||||||
|
this.isTyping = true;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// Verzamel gegevens voor de API call
|
||||||
|
const apiData = {
|
||||||
|
message: userMessage.content,
|
||||||
conversation_id: this.conversationId,
|
conversation_id: this.conversationId,
|
||||||
user_id: this.userId
|
user_id: this.userId,
|
||||||
});
|
form_values: formValues // Voeg formuliergegevens toe aan API call
|
||||||
|
};
|
||||||
|
|
||||||
if (response.success) {
|
// Verstuur bericht naar de API
|
||||||
this.addMessage(
|
const response = await this.callAPI('/api/send_message', apiData);
|
||||||
`✅ ${response.message || 'Formulier succesvol verzonden!'}`,
|
|
||||||
|
// Verberg de typing indicator
|
||||||
|
this.isTyping = false;
|
||||||
|
|
||||||
|
// Markeer het gebruikersbericht als afgeleverd
|
||||||
|
userMessage.status = 'delivered';
|
||||||
|
|
||||||
|
// Voeg AI response toe met task_id voor tracking
|
||||||
|
const aiMessage = this.addMessage(
|
||||||
|
'',
|
||||||
'ai',
|
'ai',
|
||||||
'text'
|
'text'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wis het huidige formulier (ongeacht of het succesvol was of niet)
|
if (response.task_id) {
|
||||||
this.currentInputFormData = null;
|
console.log('Monitoring Task ID: ', response.task_id);
|
||||||
} else {
|
aiMessage.taskId = response.task_id;
|
||||||
this.addMessage(
|
|
||||||
`❌ Er ging iets mis: ${response.error || 'Onbekende fout'}`,
|
|
||||||
'ai',
|
|
||||||
'text'
|
|
||||||
);
|
|
||||||
// Wis ook hier het formulier na een fout
|
|
||||||
this.currentInputFormData = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset formulier na succesvolle verzending
|
||||||
|
this.currentInputFormData = null;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error submitting form:', error);
|
console.error('Error submitting form:', error);
|
||||||
this.addMessage(
|
this.addMessage(
|
||||||
@@ -318,6 +361,7 @@ export const ChatApp = {
|
|||||||
this.currentInputFormData = null;
|
this.currentInputFormData = null;
|
||||||
} finally {
|
} finally {
|
||||||
this.isSubmittingForm = false;
|
this.isSubmittingForm = false;
|
||||||
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -125,9 +125,15 @@
|
|||||||
const hasValidForm = this.formData && this.validateForm();
|
const hasValidForm = this.formData && this.validateForm();
|
||||||
const hasValidMessage = this.localMessage.trim() && !this.isOverLimit;
|
const hasValidMessage = this.localMessage.trim() && !this.isOverLimit;
|
||||||
|
|
||||||
|
// We kunnen nu verzenden als er een geldig formulier OF een geldig bericht is
|
||||||
|
// Bij een formulier is aanvullende tekst optioneel
|
||||||
return (!this.isLoading) && (hasValidForm || hasValidMessage);
|
return (!this.isLoading) && (hasValidForm || hasValidMessage);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasFormDataToSend() {
|
||||||
|
return this.formData && this.validateForm();
|
||||||
|
},
|
||||||
|
|
||||||
sendButtonText() {
|
sendButtonText() {
|
||||||
if (this.isLoading) {
|
if (this.isLoading) {
|
||||||
return 'Verzenden...';
|
return 'Verzenden...';
|
||||||
@@ -181,20 +187,31 @@
|
|||||||
sendMessage() {
|
sendMessage() {
|
||||||
if (!this.canSend) return;
|
if (!this.canSend) return;
|
||||||
|
|
||||||
|
// Bij een formulier gaan we het formulier en optioneel bericht combineren
|
||||||
if (this.formData) {
|
if (this.formData) {
|
||||||
// Valideer het formulier
|
// Valideer het formulier
|
||||||
if (this.validateForm()) {
|
if (this.validateForm()) {
|
||||||
// Verstuur het formulier
|
// Verstuur het formulier, eventueel met aanvullende tekst
|
||||||
this.$emit('submit-form', this.formValues);
|
this.$emit('submit-form', this.formValues);
|
||||||
this.formValues = {};
|
|
||||||
}
|
}
|
||||||
} else if (this.localMessage.trim()) {
|
} else if (this.localMessage.trim()) {
|
||||||
// Verstuur normaal bericht
|
// Verstuur normaal bericht zonder formulier
|
||||||
this.$emit('send-message');
|
this.$emit('send-message');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Deze methode houden we voor de volledigheid, maar zal niet meer direct worden aangeroepen
|
getFormValuesForSending() {
|
||||||
|
// Geeft de huidige formulierwaarden terug voor verzending
|
||||||
|
return this.formValues;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset het formulier en de waarden
|
||||||
|
resetForm() {
|
||||||
|
this.formValues = {};
|
||||||
|
this.initFormValues();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Annuleer het formulier (wordt momenteel niet gebruikt)
|
||||||
cancelForm() {
|
cancelForm() {
|
||||||
this.formValues = {};
|
this.formValues = {};
|
||||||
// We sturen geen emit meer, maar het kan nuttig zijn om in de toekomst te hebben
|
// We sturen geen emit meer, maar het kan nuttig zijn om in de toekomst te hebben
|
||||||
@@ -261,7 +278,7 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
|
||||||
</div>
|
</div>
|
||||||
<!-- Dynamisch formulier container -->
|
<!-- Dynamisch formulier container -->
|
||||||
<div v-if="formData" class="dynamic-form-container" style="margin-bottom: 10px; border: 1px solid #ddd; border-radius: 8px; padding: 15px 15px 5px 15px; position: relative; background-color: #f9f9f9; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
<div v-if="formData" class="dynamic-form-container">
|
||||||
<!-- De titel wordt in DynamicForm weergegeven en niet hier om dubbele titels te voorkomen -->
|
<!-- De titel wordt in DynamicForm weergegeven en niet hier om dubbele titels te voorkomen -->
|
||||||
<div v-if="!formData.fields" style="color: red; padding: 10px;">Fout: Geen velden gevonden in formulier</div>
|
<div v-if="!formData.fields" style="color: red; padding: 10px;">Fout: Geen velden gevonden in formulier</div>
|
||||||
<dynamic-form
|
<dynamic-form
|
||||||
@@ -1,3 +1,33 @@
|
|||||||
|
// Voeg stylesheets toe voor formulier en chat berichten weergave
|
||||||
|
const addStylesheets = () => {
|
||||||
|
// Formulier stylesheet
|
||||||
|
if (!document.querySelector('link[href*="form-message.css"]')) {
|
||||||
|
const formLink = document.createElement('link');
|
||||||
|
formLink.rel = 'stylesheet';
|
||||||
|
formLink.href = '/static/assets/css/form-message.css';
|
||||||
|
document.head.appendChild(formLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat bericht stylesheet
|
||||||
|
if (!document.querySelector('link[href*="chat-message.css"]')) {
|
||||||
|
const chatLink = document.createElement('link');
|
||||||
|
chatLink.rel = 'stylesheet';
|
||||||
|
chatLink.href = '/static/assets/css/chat-message.css';
|
||||||
|
document.head.appendChild(chatLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Material Icons font stylesheet
|
||||||
|
if (!document.querySelector('link[href*="Material+Symbols+Outlined"]')) {
|
||||||
|
const iconLink = document.createElement('link');
|
||||||
|
iconLink.rel = 'stylesheet';
|
||||||
|
iconLink.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0';
|
||||||
|
document.head.appendChild(iconLink);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Laad de stylesheets
|
||||||
|
addStylesheets();
|
||||||
|
|
||||||
export const ChatMessage = {
|
export const ChatMessage = {
|
||||||
name: 'ChatMessage',
|
name: 'ChatMessage',
|
||||||
props: {
|
props: {
|
||||||
@@ -17,11 +47,38 @@ export const ChatMessage = {
|
|||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
// Zorg ervoor dat het icoon geladen wordt als iconManager beschikbaar is
|
||||||
|
if (window.iconManager && this.message.formData && this.message.formData.icon) {
|
||||||
|
window.iconManager.loadIcon(this.message.formData.icon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'message.formData.icon': {
|
||||||
|
handler(newIcon) {
|
||||||
|
if (newIcon && window.iconManager) {
|
||||||
|
window.iconManager.loadIcon(newIcon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
emits: ['image-loaded', 'retry-message', 'specialist-complete', 'specialist-error'],
|
emits: ['image-loaded', 'retry-message', 'specialist-complete', 'specialist-error'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
formVisible: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
hasFormData() {
|
||||||
|
return this.message.formData &&
|
||||||
|
((Array.isArray(this.message.formData.fields) && this.message.formData.fields.length > 0) ||
|
||||||
|
(typeof this.message.formData.fields === 'object' && Object.keys(this.message.formData.fields).length > 0));
|
||||||
|
},
|
||||||
|
hasFormValues() {
|
||||||
|
return this.message.formValues && Object.keys(this.message.formValues).length > 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSpecialistError(eventData) {
|
handleSpecialistError(eventData) {
|
||||||
console.log('ChatMessage received specialist-error event:', eventData);
|
console.log('ChatMessage received specialist-error event:', eventData);
|
||||||
@@ -90,7 +147,7 @@ export const ChatMessage = {
|
|||||||
<div :class="getMessageClass()" :data-message-id="message.id">
|
<div :class="getMessageClass()" :data-message-id="message.id">
|
||||||
<!-- Normal text messages -->
|
<!-- Normal text messages -->
|
||||||
<template v-if="message.type === 'text'">
|
<template v-if="message.type === 'text'">
|
||||||
<div class="message-content">
|
<div class="message-content" style="width: 100%;">
|
||||||
<!-- Voortgangstracker voor AI berichten met task_id - NU BINNEN DE BUBBLE -->
|
<!-- Voortgangstracker voor AI berichten met task_id - NU BINNEN DE BUBBLE -->
|
||||||
<progress-tracker
|
<progress-tracker
|
||||||
v-if="message.sender === 'ai' && message.taskId"
|
v-if="message.sender === 'ai' && message.taskId"
|
||||||
@@ -101,9 +158,79 @@ export const ChatMessage = {
|
|||||||
@specialist-error="handleSpecialistError"
|
@specialist-error="handleSpecialistError"
|
||||||
></progress-tracker>
|
></progress-tracker>
|
||||||
|
|
||||||
|
<!-- Form data display if available (alleen in user messages) -->
|
||||||
|
<div v-if="message.formValues && message.sender === 'user'" class="form-display user-form-values">
|
||||||
|
<dynamic-form
|
||||||
|
:form-data="message.formData"
|
||||||
|
:form-values="message.formValues"
|
||||||
|
:read-only="true"
|
||||||
|
hide-actions
|
||||||
|
class="message-form user-form"
|
||||||
|
></dynamic-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formulier in AI berichten -->
|
||||||
|
<div v-if="message.formData && message.sender === 'ai'" class="form-display ai-form-values" style="margin-top: 15px;">
|
||||||
|
<!-- Dynamisch toevoegen van Material Symbols Outlined voor iconen -->
|
||||||
|
<table class="form-result-table">
|
||||||
|
<thead v-if="message.formData.title || message.formData.name || message.formData.icon">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">
|
||||||
|
<div class="form-header">
|
||||||
|
<span v-if="message.formData.icon" class="material-symbols-outlined">{{ message.formData.icon }}</span>
|
||||||
|
<span>{{ message.formData.title || message.formData.name }}</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(field, fieldId) in message.formData.fields" :key="fieldId">
|
||||||
|
<td class="field-label">{{ field.name }}:</td>
|
||||||
|
<td class="field-value">
|
||||||
|
<input
|
||||||
|
v-if="field.type === 'str' || field.type === 'string' || field.type === 'int' || field.type === 'integer' || field.type === 'float'"
|
||||||
|
:type="field.type === 'int' || field.type === 'integer' || field.type === 'float' ? 'number' : 'text'"
|
||||||
|
:placeholder="field.placeholder || ''"
|
||||||
|
class="form-input"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-else-if="field.type === 'text'"
|
||||||
|
:placeholder="field.placeholder || ''"
|
||||||
|
:rows="field.rows || 3"
|
||||||
|
class="form-textarea"
|
||||||
|
></textarea>
|
||||||
|
<select
|
||||||
|
v-else-if="field.type === 'enum'"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option value="">Selecteer een optie</option>
|
||||||
|
<option
|
||||||
|
v-for="option in (field.allowedValues || field.allowed_values || [])"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div v-else-if="field.type === 'boolean'" class="toggle-switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle-input"
|
||||||
|
>
|
||||||
|
<span class="toggle-slider">
|
||||||
|
<span class="toggle-knob"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- View mode -->
|
<!-- View mode -->
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
v-if="message.content"
|
||||||
v-html="formatMessage(message.content)"
|
v-html="formatMessage(message.content)"
|
||||||
class="message-text"
|
class="message-text"
|
||||||
></div>
|
></div>
|
||||||
@@ -151,12 +151,12 @@ export const DynamicForm = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="readOnly" class="form-readonly">
|
<div v-if="readOnly" class="form-readonly" style="display: grid; grid-template-columns: 35% 65%; gap: 8px; width: 100%;">
|
||||||
<!-- Array-based fields -->
|
<!-- Array-based fields -->
|
||||||
<template v-if="Array.isArray(formData.fields)">
|
<template v-if="Array.isArray(formData.fields)">
|
||||||
<div v-for="field in formData.fields" :key="field.id || field.name" class="form-field-readonly">
|
<template v-for="field in formData.fields" :key="field.id || field.name">
|
||||||
<div class="field-label">{{ field.name }}:</div>
|
<div class="field-label" style="font-weight: 500; color: #555; padding: 4px 0;">{{ field.name }}:</div>
|
||||||
<div class="field-value">
|
<div class="field-value" style="padding: 4px 0;">
|
||||||
<template v-if="field.type === 'enum' && (field.allowedValues || field.allowed_values)">
|
<template v-if="field.type === 'enum' && (field.allowedValues || field.allowed_values)">
|
||||||
{{ localFormValues[field.id || field.name] || field.default || '-' }}
|
{{ localFormValues[field.id || field.name] || field.default || '-' }}
|
||||||
</template>
|
</template>
|
||||||
@@ -164,19 +164,19 @@ export const DynamicForm = {
|
|||||||
{{ localFormValues[field.id || field.name] ? 'Ja' : 'Nee' }}
|
{{ localFormValues[field.id || field.name] ? 'Ja' : 'Nee' }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="field.type === 'text'">
|
<template v-else-if="field.type === 'text'">
|
||||||
<div class="text-value">{{ localFormValues[field.id || field.name] || '-' }}</div>
|
<div class="text-value" style="white-space: pre-wrap;">{{ localFormValues[field.id || field.name] || '-' }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ localFormValues[field.id || field.name] || field.default || '-' }}
|
{{ localFormValues[field.id || field.name] || field.default || '-' }}
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- Object-based fields -->
|
<!-- Object-based fields -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-for="(field, fieldId) in formData.fields" :key="fieldId" class="form-field-readonly">
|
<template v-for="(field, fieldId) in formData.fields" :key="fieldId">
|
||||||
<div class="field-label">{{ field.name }}:</div>
|
<div class="field-label" style="font-weight: 500; color: #555; padding: 4px 0;">{{ field.name }}:</div>
|
||||||
<div class="field-value">
|
<div class="field-value" style="padding: 4px 0;">
|
||||||
<template v-if="field.type === 'enum' && (field.allowedValues || field.allowed_values)">
|
<template v-if="field.type === 'enum' && (field.allowedValues || field.allowed_values)">
|
||||||
{{ localFormValues[fieldId] || field.default || '-' }}
|
{{ localFormValues[fieldId] || field.default || '-' }}
|
||||||
</template>
|
</template>
|
||||||
@@ -184,13 +184,13 @@ export const DynamicForm = {
|
|||||||
{{ localFormValues[fieldId] ? 'Ja' : 'Nee' }}
|
{{ localFormValues[fieldId] ? 'Ja' : 'Nee' }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="field.type === 'text'">
|
<template v-else-if="field.type === 'text'">
|
||||||
<div class="text-value">{{ localFormValues[fieldId] || '-' }}</div>
|
<div class="text-value" style="white-space: pre-wrap;">{{ localFormValues[fieldId] || '-' }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ localFormValues[fieldId] || field.default || '-' }}
|
{{ localFormValues[fieldId] || field.default || '-' }}
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/* Animation styles for ChatInput component */
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add more ChatInput-specific styles here */
|
|
||||||
.loading-spinner {
|
|
||||||
display: inline-block;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/* Stijlen voor het FormMessage component in chatberichten */
|
|
||||||
.form-message {
|
|
||||||
background-color: #f5f8ff;
|
|
||||||
border: 1px solid #e1e8f5;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
border-bottom: 1px solid #e1e8f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 8px;
|
|
||||||
color: #4a6fa5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #3a5a80;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-fields {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-field {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-message-label {
|
|
||||||
flex: 0 0 120px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #4a6fa5;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-message-value {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #333;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-message-value.text-value {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.form-message-field {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-message-label {
|
|
||||||
flex: 0 0 100%;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-message-value {
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -117,10 +117,12 @@ def send_message():
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = request.json
|
data = request.json
|
||||||
message = data.get('message')
|
message = data.get('message', '')
|
||||||
|
form_values = data.get('form_values', {})
|
||||||
|
|
||||||
if not message:
|
# Controleer of er ofwel een bericht of formuliergegevens zijn
|
||||||
return jsonify({'error': 'No message provided'}), 400
|
if not message and not form_values:
|
||||||
|
return jsonify({'error': 'No message or form data provided'}), 400
|
||||||
|
|
||||||
tenant_id = session['tenant']['id']
|
tenant_id = session['tenant']['id']
|
||||||
specialist_id = session['specialist']['id']
|
specialist_id = session['specialist']['id']
|
||||||
@@ -134,8 +136,13 @@ def send_message():
|
|||||||
Database(tenant_id).switch_schema()
|
Database(tenant_id).switch_schema()
|
||||||
|
|
||||||
# Add user message to specialist arguments
|
# Add user message to specialist arguments
|
||||||
|
if message:
|
||||||
specialist_args['question'] = message
|
specialist_args['question'] = message
|
||||||
|
|
||||||
|
# Add form values to specialist arguments if present
|
||||||
|
if form_values:
|
||||||
|
specialist_args['form_values'] = form_values
|
||||||
|
|
||||||
current_app.logger.debug(f"Sending message to specialist: {specialist_id} for tenant {tenant_id}\n"
|
current_app.logger.debug(f"Sending message to specialist: {specialist_id} for tenant {tenant_id}\n"
|
||||||
f" with args: {specialist_args}\n"
|
f" with args: {specialist_args}\n"
|
||||||
f"with session ID: {chat_session_id}")
|
f"with session ID: {chat_session_id}")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import List, Optional
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
|
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
|
||||||
|
|
||||||
|
|
||||||
# class BehaviouralCompetence(BaseModel):
|
# class BehaviouralCompetence(BaseModel):
|
||||||
# title: str = Field(..., description="The title of the behavioural competence.")
|
# title: str = Field(..., description="The title of the behavioural competence.")
|
||||||
# description: Optional[str] = Field(None, description="The description of the behavioural competence.")
|
# description: Optional[str] = Field(None, description="The description of the behavioural competence.")
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from os import wait
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import date
|
||||||
|
from time import sleep
|
||||||
|
from crewai.flow.flow import start, listen, and_
|
||||||
|
from flask import current_app
|
||||||
|
from pydantic import BaseModel, Field, EmailStr
|
||||||
|
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
|
||||||
|
from common.extensions import cache_manager
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
||||||
|
"""
|
||||||
|
type: TRAICIE_SELECTION_SPECIALIST
|
||||||
|
type_version: 1.1
|
||||||
|
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.1"
|
||||||
|
|
||||||
|
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 Selection 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)
|
||||||
|
for i in range(3):
|
||||||
|
sleep(1)
|
||||||
|
self.ept.send_update(self.task_id, "Traicie Selection Specialist Processing", {"name": f"Processing Iteration {i}"})
|
||||||
|
|
||||||
|
# flow_results = asyncio.run(self.flow.kickoff_async(inputs=arguments.model_dump()))
|
||||||
|
# flow_state = self.flow.state
|
||||||
|
# results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
|
||||||
|
contact_form = cache_manager.specialist_forms_config_cache.get_config("PERSONAL_CONTACT_FORM", "1.0")
|
||||||
|
current_app.logger.debug(f"Contact form: {contact_form}")
|
||||||
|
results = SpecialistResult.create_for_type(self.type, self.type_version,
|
||||||
|
answer=f"Antwoord op uw vraag: {arguments.question}",
|
||||||
|
form_request=contact_form)
|
||||||
|
|
||||||
|
self.log_tuning(f"Traicie Selection 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, self.type, self.type_version)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionSpecialistInput(BaseModel):
|
||||||
|
region: str = Field(..., alias="region")
|
||||||
|
working_schedule: Optional[str] = Field(..., alias="working_schedule")
|
||||||
|
start_date: Optional[date] = Field(None, alias="vacancy_text")
|
||||||
|
language: Optional[str] = Field(None, alias="language")
|
||||||
|
interaction_mode: Optional[str] = Field(None, alias="interaction_mode")
|
||||||
|
question: Optional[str] = Field(None, alias="question")
|
||||||
|
field_values: Optional[Dict[str, Any]] = Field(None, alias="field_values")
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionSpecialistKOCriteriumScore(BaseModel):
|
||||||
|
criterium: Optional[str] = Field(None, alias="criterium")
|
||||||
|
answer: Optional[str] = Field(None, alias="answer")
|
||||||
|
score: Optional[int] = Field(None, alias="score")
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionSpecialistCompetencyScore(BaseModel):
|
||||||
|
competency: Optional[str] = Field(None, alias="competency")
|
||||||
|
answer: Optional[str] = Field(None, alias="answer")
|
||||||
|
score: Optional[int] = Field(None, alias="score")
|
||||||
|
|
||||||
|
|
||||||
|
class PersonalContactData(BaseModel):
|
||||||
|
name: str = Field(..., description="Your name", alias="name")
|
||||||
|
email: EmailStr = Field(..., description="Your Name", alias="email")
|
||||||
|
phone: str = Field(..., description="Your Phone Number", alias="phone")
|
||||||
|
address: Optional[str] = Field(None, description="Your Address", alias="address")
|
||||||
|
zip: Optional[str] = Field(None, description="Postal Code", alias="zip")
|
||||||
|
city: Optional[str] = Field(None, description="City", alias="city")
|
||||||
|
country: Optional[str] = Field(None, description="Country", alias="country")
|
||||||
|
consent: bool = Field(..., description="Consent", alias="consent")
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionSpecialistResult(SpecialistResult):
|
||||||
|
ko_criteria_scores: Optional[List[SelectionSpecialistKOCriteriumScore]] = Field(
|
||||||
|
None, alias="ko_criteria_scores"
|
||||||
|
)
|
||||||
|
competency_scores: Optional[List[SelectionSpecialistCompetencyScore]] = Field(
|
||||||
|
None, alias="competency_scores"
|
||||||
|
)
|
||||||
|
personal_contact_data: Optional[PersonalContactData] = Field(
|
||||||
|
None, alias="personal_contact_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionSpecialistFlowState(EveAIFlowState):
|
||||||
|
"""Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
|
||||||
|
input: Optional[SelectionSpecialistInput] = None
|
||||||
|
ko_criteria_scores: Optional[List[SelectionSpecialistKOCriteriumScore]] = Field(
|
||||||
|
None, alias="ko_criteria_scores"
|
||||||
|
)
|
||||||
|
competency_scores: Optional[List[SelectionSpecialistCompetencyScore]] = Field(
|
||||||
|
None, alias="competency_scores"
|
||||||
|
)
|
||||||
|
personal_contact_data: Optional[PersonalContactData] = Field(
|
||||||
|
None, alias="personal_contact_data"
|
||||||
|
)
|
||||||
|
phase: Optional[str] = Field(None, alias="phase")
|
||||||
|
interaction_mode: Optional[str] = Field(None, alias="mode")
|
||||||
|
|
||||||
|
|
||||||
|
class RoleDefinitionFlow(EveAICrewAIFlow[SelectionSpecialistFlowState]):
|
||||||
|
def __init__(self,
|
||||||
|
specialist_executor: CrewAIBaseSpecialistExecutor,
|
||||||
|
role_definition_crew: EveAICrewAICrew,
|
||||||
|
**kwargs):
|
||||||
|
super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
|
||||||
|
self.specialist_executor = specialist_executor
|
||||||
|
self.role_definition_crew = role_definition_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
|
||||||
Reference in New Issue
Block a user