diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index 3145502..4fc1101 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -1,6 +1,6 @@ { - "dist/chat-client.js": "dist/chat-client.be407684.js", - "dist/chat-client.css": "dist/chat-client.00145e73.css", + "dist/chat-client.js": "dist/chat-client.825210dd.js", + "dist/chat-client.css": "dist/chat-client.568d7be7.css", "dist/main.js": "dist/main.6a617099.js", "dist/main.css": "dist/main.7182aac3.css" } \ No newline at end of file diff --git a/config/tasks/traicie/TRAICIE_AFFIRMATIVE_ANSWER_CHECK_TASK/1.0.0.yaml b/config/tasks/traicie/TRAICIE_AFFIRMATIVE_ANSWER_CHECK_TASK/1.0.0.yaml index 0fd3038..e5126ae 100644 --- a/config/tasks/traicie/TRAICIE_AFFIRMATIVE_ANSWER_CHECK_TASK/1.0.0.yaml +++ b/config/tasks/traicie/TRAICIE_AFFIRMATIVE_ANSWER_CHECK_TASK/1.0.0.yaml @@ -10,11 +10,22 @@ task_description: > €€€{history}€€€ (In this history, user interactions are preceded by 'HUMAN', and your interactions with 'AI'.) + Take into account the last question asked by the you, the AI. - Check if the user has given an affirmative answer or not. + Check if the user has given an affirmative answer to that last question or not. Please note that this answer can be very short: - Affirmative answers: e.g. Yes, OK, Sure, Of Course - Negative answers: e.g. No, not really, No, I'd rather not. + Also note that users may use emoticons, emojis, or other symbols to express their affirmative answers. + - Affirmative answers: e.g. 👍🏼 , 👌🏼 , ☺️ + - Negative answers: e.g. 👎🏼 , 🙅🏼 , 😒 + Finally, users may use a direct answer to the last question asked: + Example 1: + - Question: "Do you have any other questions, or shall we start the interview to see if there’s a match with the job?" + - Affirmative Answer: "Start the interview" or "Start please" + Example 2: + - Question: "Is there anything still on your mind, or shall we begin the conversation to explore the match?" + - Affirmative Answer: "Let's start exploring" or "Let's go" Please consider that the answer will be given in {language}! diff --git a/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue b/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue index 9d5f94d..092c27f 100644 --- a/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue +++ b/eveai_chat_client/static/assets/vue-components/ConsentRichText.vue @@ -37,7 +37,7 @@ export default { const source = (this.template || ''); // 2) parse only allowed tags ... and ... - const pattern = /<(privacy|terms)>([\s\S]*?)<\/\1>/gi; + const pattern = /<(dpa|terms)>([\s\S]*?)<\/\1>/gi; const out = []; let lastIndex = 0; let match; @@ -62,8 +62,17 @@ export default { }, methods: { emitClick(kind) { - if (kind === 'dpa') this.$emit('open-dpa'); - if (kind === 'terms') this.$emit('open-terms'); + // Debug logging to trace click events for consent links + console.log('[ConsentRichText] emitClick called with kind =', kind); + + if (kind === 'dpa') { + console.log('[ConsentRichText] Emitting open-dpa event'); + this.$emit('open-dpa'); + } + if (kind === 'terms') { + console.log('[ConsentRichText] Emitting open-terms event'); + this.$emit('open-terms'); + } } } }; diff --git a/eveai_chat_client/static/assets/vue-components/DynamicForm.vue b/eveai_chat_client/static/assets/vue-components/DynamicForm.vue index d92697f..a044fd1 100644 --- a/eveai_chat_client/static/assets/vue-components/DynamicForm.vue +++ b/eveai_chat_client/static/assets/vue-components/DynamicForm.vue @@ -19,7 +19,7 @@ :field-id="field.id || field.name" :model-value="localFormValues[field.id || field.name]" @update:model-value="updateFieldValue(field.id || field.name, $event)" - @open-privacy-modal="openPrivacyModal" + @open-dpa-modal="openDpaModal" @open-terms-modal="openTermsModal" @keydown-enter="handleEnterKey" /> @@ -32,7 +32,7 @@ :field-id="fieldId" :model-value="localFormValues[fieldId]" @update:model-value="updateFieldValue(fieldId, $event)" - @open-privacy-modal="openPrivacyModal" + @open-dpa-modal="openDpaModal" @open-terms-modal="openTermsModal" @keydown-enter="handleEnterKey" /> @@ -199,12 +199,19 @@ export default { // Basic validation - check required fields const missingFields = []; + // Extra consent-validatie: detecteer consent velden en controleer of alle consents geaccepteerd zijn. + // We maken dit toekomstvast voor meerdere consent-velden. + let hasConsentField = false; + let allConsentsAccepted = true; + if (Array.isArray(this.formData.fields)) { // Valideer array-gebaseerde velden this.formData.fields.forEach(field => { const fieldId = field.id || field.name; + const value = this.localFormValues[fieldId]; + + // Basis required-validatie if (field.required) { - const value = this.localFormValues[fieldId]; // Voor boolean velden is false een geldige waarde if (field.type === 'boolean') { // Boolean velden zijn altijd geldig als ze een boolean waarde hebben @@ -220,12 +227,21 @@ export default { } } } + + // Consent-detectie en -validatie (ongeacht required-vlag) + if (field.type === 'boolean' && field.meta && field.meta.kind === 'consent') { + hasConsentField = true; + if (value !== true) { + allConsentsAccepted = false; + } + } }); } else { // Valideer object-gebaseerde velden Object.entries(this.formData.fields).forEach(([fieldId, field]) => { + const value = this.localFormValues[fieldId]; + if (field.required) { - const value = this.localFormValues[fieldId]; // Voor boolean velden is false een geldige waarde if (field.type === 'boolean') { // Boolean velden zijn altijd geldig als ze een boolean waarde hebben @@ -241,10 +257,27 @@ export default { } } } + + // Consent-detectie en -validatie (ongeacht required-vlag) + if (field.type === 'boolean' && field.meta && field.meta.kind === 'consent') { + hasConsentField = true; + if (value !== true) { + allConsentsAccepted = false; + } + } }); } - return missingFields.length === 0; + const isBaseValid = missingFields.length === 0; + + if (!hasConsentField) { + // Geen speciale consentvelden: behoud bestaand gedrag + return isBaseValid; + } + + // Als er één of meer consentvelden zijn, zijn we alleen geldig als + // zowel de basisvalidatie als alle consents geaccepteerd zijn. + return isBaseValid && allConsentsAccepted; }, // Title display mode configuration titleDisplayMode() { @@ -479,11 +512,13 @@ export default { }, // Modal handling methods - openPrivacyModal() { + openDpaModal() { + console.log('[DynamicForm] openDpaModal called'); this.loadContent('dpa'); }, openTermsModal() { + console.log('[DynamicForm] openTermsModal called'); this.loadContent('terms'); }, @@ -504,6 +539,8 @@ export default { async loadContent(contentType) { const title = contentType === 'dpa' ? 'Data Privacy Agreement' : 'Terms & Conditions'; const contentUrl = `${this.apiPrefix}/${contentType}`; + + console.log('[DynamicForm] Loading content from:', contentUrl); // Use the composable to show modal and load content await this.contentModal.showModal({ @@ -514,11 +551,19 @@ export default { // Handle Enter key press in form fields handleEnterKey(event) { - console.log('DynamicForm: Enter event received, emitting form-enter-pressed'); + console.log('DynamicForm: Enter event received'); // Prevent default form submission event.preventDefault(); - // Emit event to parent (ChatInput) to trigger send - this.$emit('form-enter-pressed'); + + // Alleen submit toelaten als het formulier (inclusief consentvelden) + // geldig is. Hiermee worden keyboard-shortcuts uitgeschakeld zolang + // consent niet is gegeven of andere vereiste velden ontbreken. + if (this.isFormValid && !this.isSubmittingForm && !this.isSubmitting) { + // Emit event to parent (ChatInput) to trigger send + this.$emit('form-enter-pressed'); + } else { + console.log('DynamicForm: Enter ignored because form is not valid or is submitting'); + } }, // Focus management - auto-focus on first form field diff --git a/eveai_chat_client/static/assets/vue-components/FormField.vue b/eveai_chat_client/static/assets/vue-components/FormField.vue index 6de1755..22ae7a6 100644 --- a/eveai_chat_client/static/assets/vue-components/FormField.vue +++ b/eveai_chat_client/static/assets/vue-components/FormField.vue @@ -111,7 +111,7 @@ :template="texts.consentRich" :aria-privacy="texts.ariaPrivacy || 'Open dpa statement in a dialog'" :aria-terms="texts.ariaTerms || 'Open terms and conditions in a dialog'" - @open-privacy="openPrivacyModal" + @open-dpa="openDpaModal" @open-terms="openTermsModal" /> * @@ -234,7 +234,7 @@ export default { texts() { // Validate that consentRich exists and includes both required tags; otherwise fallback to English base const hasValidRich = (t) => t && typeof t.consentRich === 'string' - && /[\s\S]*?<\/privacy>/.test(t.consentRich) + && /[\s\S]*?<\/dpa>/.test(t.consentRich) && /[\s\S]*?<\/terms>/.test(t.consentRich); // 1) Prefer backend-provided rich string on the field's meta (already localized) @@ -331,10 +331,12 @@ export default { this.value = file; } }, - openPrivacyModal() { + openDpaModal() { + console.log('[FormField] openDpaModal emitting open-dpa-modal'); this.$emit('open-dpa-modal'); }, openTermsModal() { + console.log('[FormField] openTermsModal emitting open-terms-modal'); this.$emit('open-terms-modal'); }, diff --git a/eveai_chat_client/views/chat_views.py b/eveai_chat_client/views/chat_views.py index 8815957..e1614e3 100644 --- a/eveai_chat_client/views/chat_views.py +++ b/eveai_chat_client/views/chat_views.py @@ -385,7 +385,7 @@ def translate(): }), 500 -@chat_bp.route('/privacy', methods=['GET']) +@chat_bp.route('/dpa', methods=['GET']) def privacy_statement(): """ Public AJAX endpoint for dpa statement content