diff --git a/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml b/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml index 7e5a02d..6eee03f 100644 --- a/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml +++ b/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml @@ -1,6 +1,7 @@ +type: "PERSONAL_CONTACT_FORM" version: "1.0.0" name: "Personal Contact Form" -icon: "call" +icon: "person" fields: name: name: "Name" @@ -17,24 +18,28 @@ fields: type: "str" description: "Your Phone Number" required: true - Address: + address: name: "Address" - type: "text" + type: "string" description: "Your Address" required: false - status: - name: "Marital Status" - type: "enum" - description: "Your Marital Status" + zip: + name: "Postal Code" + type: "string" + description: "Postal Code" required: false - default: "single" - allowed_values: - - "single" - - "married" - - "divorced" - can_contact: - name: "Allow Contact" + city: + name: "City" + type: "string" + description: "City" + required: false + country: + name: "Country" + type: "string" + description: "Country" + required: false + consent: + name: "Consent" type: "boolean" - description: "Allow us to contact you?" + description: "Consent" required: true - default: false \ No newline at end of file diff --git a/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml b/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml new file mode 100644 index 0000000..cbcf484 --- /dev/null +++ b/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml @@ -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 diff --git a/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.2.0.yaml b/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.2.0.yaml new file mode 100644 index 0000000..55389ff --- /dev/null +++ b/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.2.0.yaml @@ -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" \ No newline at end of file diff --git a/config/type_defs/specialist_form_types.py b/config/type_defs/specialist_form_types.py index 57079e4..52de307 100644 --- a/config/type_defs/specialist_form_types.py +++ b/config/type_defs/specialist_form_types.py @@ -1,7 +1,11 @@ # Specialist Form Types SPECIALIST_FORM_TYPES = { "PERSONAL_CONTACT_FORM": { - "name": "Contact Form", + "name": "Personal Contact Form", "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", + }, } \ No newline at end of file diff --git a/docker/update_chat_client_statics.sh b/docker/update_chat_client_statics.sh index ffa0c9f..a4af404 100755 --- a/docker/update_chat_client_statics.sh +++ b/docker/update_chat_client_statics.sh @@ -3,7 +3,7 @@ # Script to copy eveai_chat_client/static files to nginx/static # without overwriting existing files -SRC_DIR="../eveai_chat_client/static" +SRC_DIR="../eveai_chat_client/static/assets" DEST_DIR="../nginx/static/assets" # Check if source directory exists diff --git a/eveai_chat_client/static/css/chat-components.css b/eveai_chat_client/static/assets/css/chat-components.css similarity index 98% rename from eveai_chat_client/static/css/chat-components.css rename to eveai_chat_client/static/assets/css/chat-components.css index edb070c..e572293 100644 --- a/eveai_chat_client/static/css/chat-components.css +++ b/eveai_chat_client/static/assets/css/chat-components.css @@ -32,7 +32,6 @@ border-radius: 15px; background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); - border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 20px rgba(0,0,0,0.1); width: 100%; max-width: 1000px; /* Optimale breedte */ @@ -377,7 +376,7 @@ /* User message bubble styling */ .message.user .message-content { - background: linear-gradient(135deg, #007bff, #0056b3); + background: rgba(0, 0, 0, 0.1); color: white; border-bottom-right-radius: 4px; } @@ -385,9 +384,8 @@ /* AI/Bot message bubble styling */ .message.ai .message-content, .message.bot .message-content { - background: #f8f9fa; + background: rgba(255, 255, 255, 0.1); color: #212529; - border: 1px solid #e9ecef; border-bottom-left-radius: 4px; margin-right: 60px; } @@ -670,7 +668,6 @@ /* Progress Tracker Styling */ .progress-tracker { margin: 8px 0; - border: 1px solid #e9ecef; border-radius: 8px; background: #f8f9fa; overflow: hidden; @@ -683,8 +680,7 @@ } .progress-tracker.completed { - border-color: #28a745; - background: #d4edda; + background: rgba(155, 255, 155, 0.1); } .progress-header { diff --git a/eveai_chat_client/static/assets/css/chat-input.css b/eveai_chat_client/static/assets/css/chat-input.css new file mode 100644 index 0000000..7eb4b3b --- /dev/null +++ b/eveai_chat_client/static/assets/css/chat-input.css @@ -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; +} diff --git a/eveai_chat_client/static/assets/css/chat-message.css b/eveai_chat_client/static/assets/css/chat-message.css new file mode 100644 index 0000000..6302c93 --- /dev/null +++ b/eveai_chat_client/static/assets/css/chat-message.css @@ -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; +} diff --git a/eveai_chat_client/static/css/chat.css b/eveai_chat_client/static/assets/css/chat.css similarity index 100% rename from eveai_chat_client/static/css/chat.css rename to eveai_chat_client/static/assets/css/chat.css diff --git a/eveai_chat_client/static/assets/css/form-message.css b/eveai_chat_client/static/assets/css/form-message.css new file mode 100644 index 0000000..90220ba --- /dev/null +++ b/eveai_chat_client/static/assets/css/form-message.css @@ -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%; +} diff --git a/eveai_chat_client/static/css/form.css b/eveai_chat_client/static/assets/css/form.css similarity index 100% rename from eveai_chat_client/static/css/form.css rename to eveai_chat_client/static/assets/css/form.css diff --git a/eveai_chat_client/static/js/chat-app.js b/eveai_chat_client/static/assets/js/chat-app.js similarity index 88% rename from eveai_chat_client/static/js/chat-app.js rename to eveai_chat_client/static/assets/js/chat-app.js index d253aa3..94ad30b 100644 --- a/eveai_chat_client/static/js/chat-app.js +++ b/eveai_chat_client/static/assets/js/chat-app.js @@ -170,21 +170,22 @@ export const ChatApp = { }, // Message management - addMessage(content, sender, type = 'text', formData = null) { + addMessage(content, sender, type = 'text', formData = null, formValues = null) { const message = { id: this.messageIdCounter++, content, sender, type, formData, + formValues, timestamp: new Date().toISOString(), status: sender === 'user' ? 'sent' : 'delivered' }; this.allMessages.push(message); - // Initialize form values if it's a form - if (type === 'form' && formData) { + // Initialize form values if it's a form and no values were provided + if (type === 'form' && formData && !formValues) { // Vue 3 compatibele manier om reactieve objecten bij te werken this.formValues[message.id] = {}; formData.fields.forEach(field => { @@ -203,19 +204,32 @@ export const ChatApp = { 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) { this.currentMessage = value; }, - // Message sending + // Message sending (alleen voor gewone tekstberichten, geen formulieren) async sendMessage() { const text = this.currentMessage.trim(); + + // Controleer of we kunnen verzenden if (!text || this.isLoading) return; - console.log('Sending message:', text); + console.log('Sending text message:', text); // Add user message const userMessage = this.addMessage(text, 'user', 'text'); + + // Wis input this.currentMessage = ''; // Show typing and loading state @@ -223,11 +237,14 @@ export const ChatApp = { this.isLoading = true; try { - const response = await this.callAPI('/api/send_message', { + // Verzamel gegevens voor de API call + const apiData = { message: text, conversation_id: this.conversationId, user_id: this.userId - }); + }; + + const response = await this.callAPI('/api/send_message', apiData); // Hide typing indicator this.isTyping = false; @@ -278,35 +295,61 @@ export const ChatApp = { 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 { - const response = await this.callAPI('/api/submit_form', { - formData: formValues, - formType: this.currentInputFormData.title, + // Maak een user message met formuliergegevens én eventuele tekst + const userMessage = this.addMessage( + 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, - user_id: this.userId - }); + user_id: this.userId, + form_values: formValues // Voeg formuliergegevens toe aan API call + }; - if (response.success) { - this.addMessage( - `✅ ${response.message || 'Formulier succesvol verzonden!'}`, - 'ai', - 'text' - ); + // Verstuur bericht naar de API + const response = await this.callAPI('/api/send_message', apiData); - // Wis het huidige formulier (ongeacht of het succesvol was of niet) - this.currentInputFormData = null; - } else { - this.addMessage( - `❌ Er ging iets mis: ${response.error || 'Onbekende fout'}`, - 'ai', - 'text' - ); - // Wis ook hier het formulier na een fout - this.currentInputFormData = null; + // 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', + 'text' + ); + + if (response.task_id) { + console.log('Monitoring Task ID: ', response.task_id); + aiMessage.taskId = response.task_id; } + // Reset formulier na succesvolle verzending + this.currentInputFormData = null; + } catch (error) { console.error('Error submitting form:', error); this.addMessage( @@ -318,6 +361,7 @@ export const ChatApp = { this.currentInputFormData = null; } finally { this.isSubmittingForm = false; + this.isLoading = false; } }, diff --git a/eveai_chat_client/static/js/components/ChatInput.js b/eveai_chat_client/static/assets/js/components/ChatInput.js similarity index 92% rename from eveai_chat_client/static/js/components/ChatInput.js rename to eveai_chat_client/static/assets/js/components/ChatInput.js index d87f60c..705181a 100644 --- a/eveai_chat_client/static/js/components/ChatInput.js +++ b/eveai_chat_client/static/assets/js/components/ChatInput.js @@ -125,9 +125,15 @@ const hasValidForm = this.formData && this.validateForm(); 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); }, + hasFormDataToSend() { + return this.formData && this.validateForm(); + }, + sendButtonText() { if (this.isLoading) { return 'Verzenden...'; @@ -181,20 +187,31 @@ sendMessage() { if (!this.canSend) return; + // Bij een formulier gaan we het formulier en optioneel bericht combineren if (this.formData) { // Valideer het formulier if (this.validateForm()) { - // Verstuur het formulier + // Verstuur het formulier, eventueel met aanvullende tekst this.$emit('submit-form', this.formValues); - this.formValues = {}; } } else if (this.localMessage.trim()) { - // Verstuur normaal bericht + // Verstuur normaal bericht zonder formulier 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() { this.formValues = {}; // We sturen geen emit meer, maar het kan nuttig zijn om in de toekomst te hebben @@ -261,7 +278,7 @@ -