From 14273b8a7061b9ebb84615363e7153e36637ec8a Mon Sep 17 00:00:00 2001 From: Josako Date: Thu, 27 Nov 2025 11:32:46 +0100 Subject: [PATCH 1/5] - Full implementation of tab bar next to logo in mobile client - Customisation option in Tenant Make - Splitting all controls in the newly created tabs --- common/utils/chat_utils.py | 5 +- .../CHAT_CLIENT_CUSTOMISATION/1.0.0.yaml | 15 + config/static-manifest/manifest.json | 4 +- .../static/assets/vue-components/ChatApp.vue | 281 +++++++++++++++--- .../assets/vue-components/ChatInput.vue | 1 + .../assets/vue-components/ChatMessage.vue | 31 +- .../assets/vue-components/MobileHeader.vue | 71 +---- .../assets/vue-components/MobileTabBar.vue | 113 +++++++ .../vue-components/SideBarMobileSetup.vue | 118 ++++++++ eveai_chat_client/templates/base.html | 5 + nginx/frontend_src/js/chat-client.js | 85 +----- 11 files changed, 524 insertions(+), 205 deletions(-) create mode 100644 eveai_chat_client/static/assets/vue-components/MobileTabBar.vue create mode 100644 eveai_chat_client/static/assets/vue-components/SideBarMobileSetup.vue diff --git a/common/utils/chat_utils.py b/common/utils/chat_utils.py index 2199445..f2a5354 100644 --- a/common/utils/chat_utils.py +++ b/common/utils/chat_utils.py @@ -36,7 +36,10 @@ def get_default_chat_customisation(tenant_customisation=None): 'ai_message_text_color': '#212529', 'human_message_background': '#212529', 'human_message_text_color': '#ffffff', - 'human_message_inactive_text_color': '#808080' + 'human_message_inactive_text_color': '#808080', + 'tab_background': '#0a0a0a', + 'tab_icon_active_color': '#ffffff', + 'tab_icon_inactive_color': '#f0f0f0', } # If no tenant customization is provided, return the defaults diff --git a/config/customisations/globals/CHAT_CLIENT_CUSTOMISATION/1.0.0.yaml b/config/customisations/globals/CHAT_CLIENT_CUSTOMISATION/1.0.0.yaml index 7723c07..701c668 100644 --- a/config/customisations/globals/CHAT_CLIENT_CUSTOMISATION/1.0.0.yaml +++ b/config/customisations/globals/CHAT_CLIENT_CUSTOMISATION/1.0.0.yaml @@ -87,6 +87,21 @@ configuration: description: "Human Message Inactive Text Color" type: "color" required: false + tab_background: + name: "Tab Background Color" + description: "Tab Background Color" + type: "color" + required: false + tab_icon_active_color: + name: "Tab Icon Active Color" + description: "Tab Icon Active Color" + type: "color" + required: false + tab_icon_inactive_color: + name: "Tab Icon Inactive Color" + description: "Tab Icon Inactive Color" + type: "color" + required: false metadata: author: "Josako" date_added: "2024-06-06" diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index f2145d6..188b548 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -1,6 +1,6 @@ { - "dist/chat-client.js": "dist/chat-client.f8ee4d5a.js", - "dist/chat-client.css": "dist/chat-client.2fffefae.css", + "dist/chat-client.js": "dist/chat-client.e8905ecb.js", + "dist/chat-client.css": "dist/chat-client.d344ebba.css", "dist/main.js": "dist/main.6a617099.js", "dist/main.css": "dist/main.7182aac3.css" } \ No newline at end of file diff --git a/eveai_chat_client/static/assets/vue-components/ChatApp.vue b/eveai_chat_client/static/assets/vue-components/ChatApp.vue index 6a4aa45..93d9a74 100644 --- a/eveai_chat_client/static/assets/vue-components/ChatApp.vue +++ b/eveai_chat_client/static/assets/vue-components/ChatApp.vue @@ -1,38 +1,106 @@ active_text_color diff --git a/eveai_chat_client/static/assets/vue-components/SideBarMobileSetup.vue b/eveai_chat_client/static/assets/vue-components/SideBarMobileSetup.vue new file mode 100644 index 0000000..cbdecc2 --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/SideBarMobileSetup.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/eveai_chat_client/templates/base.html b/eveai_chat_client/templates/base.html index 427df33..c849823 100644 --- a/eveai_chat_client/templates/base.html +++ b/eveai_chat_client/templates/base.html @@ -44,6 +44,11 @@ --human-message-background: {{ customisation.human_message_background|default('#ffffff') }}; --human-message-text-color: {{ customisation.human_message_text_color|default('#212529') }}; + /* Mobe Tab Bar Colors */ + --tab-background: {{ customisation.tab_background|default('#0a0a0a') }}; + --tab-icon-active-color: {{ customisation.tab_icon_active_color|default('#ffffff') }}; + --tab-icon-inactive-color: {{ customisation.tab_icon_inactive_color|default('#f0f0f0') }}; + } diff --git a/nginx/frontend_src/js/chat-client.js b/nginx/frontend_src/js/chat-client.js index 901911f..89bb79c 100644 --- a/nginx/frontend_src/js/chat-client.js +++ b/nginx/frontend_src/js/chat-client.js @@ -30,7 +30,6 @@ import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-compo import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue'; import ChatRoot from '../../../eveai_chat_client/static/assets/vue-components/ChatRoot.vue'; import SideBar from '../../../eveai_chat_client/static/assets/vue-components/SideBar.vue'; -import MobileHeader from '../../../eveai_chat_client/static/assets/vue-components/MobileHeader.vue'; // VueUse-setup voor de chatclient (maakt composables beschikbaar via window.VueUse) import './vueuse-setup.js'; @@ -51,9 +50,6 @@ document.addEventListener('DOMContentLoaded', function() { // Initialiseer sidebar (vervangt fillSidebarExplanation en initializeLanguageSelector) initializeSidebar(); - // Initialiseer mobile header - initializeMobileHeader(); - // Initialiseer chat app (simpel) initializeChatApp(); }); @@ -121,85 +117,8 @@ function initializeSidebar() { } } -/** - * Initialiseert de mobile header component - */ -function initializeMobileHeader() { - const container = document.getElementById('mobile-header-container'); - - if (!container) { - console.error('#mobile-header-container niet gevonden'); - return; - } - - try { - // Maak props voor de component - const props = { - tenantMake: { - name: window.chatConfig.tenantMake?.name || '', - logo_url: window.chatConfig.tenantMake?.logo_url || '', - subtitle: window.chatConfig.tenantMake?.subtitle || '' - }, - initialLanguage: window.chatConfig.language || 'nl', - supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {}, - allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'], - apiPrefix: window.chatConfig.apiPrefix || '' - }; - - // Mount de component - const app = createApp(MobileHeader, props); - - // Create and provide LanguageProvider for mobile header components - const initialLanguage = window.chatConfig?.language || 'nl'; - const apiPrefix = window.chatConfig?.apiPrefix || ''; - const languageProvider = createLanguageProvider(initialLanguage, apiPrefix); - app.provide(LANGUAGE_PROVIDER_KEY, languageProvider); - - // Error handler - app.config.errorHandler = (err, vm, info) => { - console.error('🚨 [Vue Error in MobileHeader]', err); - console.error('Component:', vm); - console.error('Error Info:', info); - }; - - const mountedApp = app.mount(container); - - // Dynamisch de headerhoogte doorgeven aan CSS - const updateHeaderHeightVar = () => { - const isMobile = window.matchMedia('(max-width: 768px)').matches; - if (!isMobile) { - document.documentElement.style.removeProperty('--mobile-header-height'); - return; - } - const h = container.offsetHeight || 60; // fallback - document.documentElement.style.setProperty('--mobile-header-height', `${h}px`); - }; - - // Initieel instellen en bij gebeurtenissen herberekenen - requestAnimationFrame(updateHeaderHeightVar); - window.addEventListener('resize', updateHeaderHeightVar); - - // Listen to language change events and update the mobile header's language provider - const languageChangeHandler = (event) => { - if (event.detail && event.detail.language) { - console.log('MobileHeader: Received language change event:', event.detail.language); - languageProvider.setLanguage(event.detail.language); - // taalwissel kan headerhoogte veranderen - requestAnimationFrame(updateHeaderHeightVar); - } - }; - document.addEventListener('language-changed', languageChangeHandler); - - // Store the handler for cleanup if needed - mountedApp._languageChangeHandler = languageChangeHandler; - mountedApp._updateHeaderHeightVar = updateHeaderHeightVar; - - console.log('✅ MobileHeader component successfully mounted with LanguageProvider en dynamische headerhoogte'); - return mountedApp; - } catch (error) { - console.error('🚨 [CRITICAL ERROR] Bij initialiseren mobile header:', error); - } -} +// initializeMobileHeader is verwijderd; de mobiele header wordt nu volledig +// binnen ChatApp.vue beheerd. /** * Initialiseert de chat app (Vue component) From b1d8c9a17d0cccdc09867f7ebd31110bf47e8dcb Mon Sep 17 00:00:00 2001 From: Josako Date: Fri, 28 Nov 2025 10:04:31 +0100 Subject: [PATCH 2/5] - Small changes to allow for keyboard input, not finished --- config/static-manifest/manifest.json | 4 +-- eveai_chat_client/static/assets/css/chat.css | 26 +++++++++++++++++-- .../static/assets/vue-components/ChatApp.vue | 24 +++++++++++------ .../assets/vue-components/SafeViewport.vue | 3 +++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index 188b548..ba22821 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -1,6 +1,6 @@ { - "dist/chat-client.js": "dist/chat-client.e8905ecb.js", - "dist/chat-client.css": "dist/chat-client.d344ebba.css", + "dist/chat-client.js": "dist/chat-client.5b709f8c.js", + "dist/chat-client.css": "dist/chat-client.cb306abb.css", "dist/main.js": "dist/main.6a617099.js", "dist/main.css": "dist/main.7182aac3.css" } \ No newline at end of file diff --git a/eveai_chat_client/static/assets/css/chat.css b/eveai_chat_client/static/assets/css/chat.css index 6bb3633..d662fb7 100644 --- a/eveai_chat_client/static/assets/css/chat.css +++ b/eveai_chat_client/static/assets/css/chat.css @@ -21,7 +21,9 @@ /* App container layout */ .app-container { display: flex; - /* Use visual viewport variable when available */ + /* Op desktop gebruiken we de veilige viewporthoogte direct; op mobiel + laten we html/body de hoogte bepalen en neemt de app-container + eenvoudig 100% daarvan in via de media query verderop. */ min-height: 0; height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100); width: 100%; @@ -93,7 +95,7 @@ display: flex; flex-direction: column; min-height: 0; - height: auto; /* prefer dynamic viewport on desktop */ + height: auto; /* desktop: dynamische hoogte, op mobiel overschreven */ } .chat-container { @@ -103,6 +105,26 @@ min-height: 0; /* laat kinderen (ChatApp) krimpen */ } +/* Op mobiel sluiten we de volledige content-kolom strak aan op de veilige + viewporthoogte zodat alleen de chatcontent zelf kan scrollen en niet de + gehele pagina wanneer het toetsenbord opent. */ +@media (max-width: 768px) { + .app-container { + height: 100%; + } + + .content-area { + height: 100%; + overflow: hidden; + } + + .chat-container { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + } +} + html, body { height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100); min-height: 0; diff --git a/eveai_chat_client/static/assets/vue-components/ChatApp.vue b/eveai_chat_client/static/assets/vue-components/ChatApp.vue index 93d9a74..deac7c4 100644 --- a/eveai_chat_client/static/assets/vue-components/ChatApp.vue +++ b/eveai_chat_client/static/assets/vue-components/ChatApp.vue @@ -1,7 +1,7 @@ active_text_color @@ -173,6 +173,15 @@ export default { if (this._resizeObserver) this._resizeObserver.disconnect(); }, methods: { + handleSpecialistCompleteFromMessage(eventData) { + console.log('🧐 [MessageHistory] specialist-complete ontvangen van ChatMessage, bubbelt naar parent:', eventData); + this.$emit('specialist-complete', eventData); + }, + + handleSpecialistErrorFromMessage(eventData) { + console.log('🧐 [MessageHistory] specialist-error ontvangen van ChatMessage, bubbelt naar parent:', eventData); + this.$emit('specialist-error', eventData); + }, async handleLanguageChange(event) { // Controleer of dit het eerste bericht is in een gesprek met maar één bericht if (this.messages.length === 1 && this.messages[0].sender === 'ai') { diff --git a/eveai_chat_client/static/assets/vue-components/MobileChatShell.vue b/eveai_chat_client/static/assets/vue-components/MobileChatShell.vue new file mode 100644 index 0000000..fc1f3b7 --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/MobileChatShell.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/nginx/frontend_src/js/chat-client.js b/nginx/frontend_src/js/chat-client.js index 89bb79c..30e56f4 100644 --- a/nginx/frontend_src/js/chat-client.js +++ b/nginx/frontend_src/js/chat-client.js @@ -27,8 +27,10 @@ console.log('Components loaded:', Object.keys(Components)); // Import specifieke componenten import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-components/LanguageSelector.vue'; -import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue'; +// import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue'; import ChatRoot from '../../../eveai_chat_client/static/assets/vue-components/ChatRoot.vue'; +import DesktopChatShell from '../../../eveai_chat_client/static/assets/vue-components/DesktopChatShell.vue'; +import MobileChatShell from '../../../eveai_chat_client/static/assets/vue-components/MobileChatShell.vue'; import SideBar from '../../../eveai_chat_client/static/assets/vue-components/SideBar.vue'; // VueUse-setup voor de chatclient (maakt composables beschikbaar via window.VueUse) @@ -131,27 +133,43 @@ function initializeChatApp() { } try { - if (!ChatApp) { - throw new Error('🚨 [CRITICAL ERROR] ChatApp component niet gevonden'); - } - // Extra verificatie dat alle sub-componenten beschikbaar zijn if (!Components.MessageHistory || !Components.ChatInput || !Components.TypingIndicator || !Components.ChatMessage) { console.warn('⚠️ [WARN] Niet alle benodigde sub-componenten zijn geladen!'); } - // Maak props voor de component - const props = { + // Maak props voor de shells / CoreChatApp + const baseProps = { apiPrefix: window.chatConfig.apiPrefix || '', conversationId: window.chatConfig.conversationId || 'default', userId: window.chatConfig.userId || null, userName: window.chatConfig.userName || '', initialLanguage: window.chatConfig.language || 'nl', supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {}, - allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'] + allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'], + tenantName: (window.chatConfig.tenantMake && window.chatConfig.tenantMake.name) || 'EveAI', + tenantSubtitle: (window.chatConfig.tenantMake && window.chatConfig.tenantMake.subtitle) || '', + tenantLogoUrl: (window.chatConfig.tenantMake && window.chatConfig.tenantMake.logo_url) || '', + explanationText: window.chatConfig.explanation || '', + settings: window.chatConfig.settings || {} }; + // Bepaal shell-type: expliciete config heeft voorrang, anders breakpoint + const layoutMode = window.chatConfig.layoutMode || 'auto'; + const isMobileBreakpoint = window.innerWidth <= 768; + let ShellComponent; + + if (layoutMode === 'desktop') { + ShellComponent = DesktopChatShell; + } else if (layoutMode === 'mobile') { + ShellComponent = MobileChatShell; + } else { + ShellComponent = isMobileBreakpoint ? MobileChatShell : DesktopChatShell; + } + + const props = { shellComponent: ShellComponent, shellProps: baseProps }; + // Mount de component via ChatRoot zodat SafeViewport de layout kan beheren const app = createApp(ChatRoot, props); From 94b805e0eb7cfbab12b39c95e4837d7a6453507f Mon Sep 17 00:00:00 2001 From: Josako Date: Tue, 2 Dec 2025 12:15:50 +0100 Subject: [PATCH 4/5] - TRA-89 - Problem solved where connection could get lost in sync between client and backend - TRA-98 - End user could continue without accepting dpa & terms - TRA-96 - Multiple-choice questions in mobile client not scrolling -> Solved by introducing new client layout - TRA-101 - DPA-link was not working - TRA-102 - Wrong responses when looking for affirmative answers. --- config/static-manifest/manifest.json | 4 +- .../1.0.0.yaml | 13 +++- .../assets/vue-components/ConsentRichText.vue | 15 ++++- .../assets/vue-components/DynamicForm.vue | 63 ++++++++++++++++--- .../assets/vue-components/FormField.vue | 8 ++- eveai_chat_client/views/chat_views.py | 2 +- 6 files changed, 86 insertions(+), 19 deletions(-) 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 From e30fe7807c516cad36fc534c4a2ee39a984a89f2 Mon Sep 17 00:00:00 2001 From: Josako Date: Tue, 2 Dec 2025 13:08:39 +0100 Subject: [PATCH 5/5] - Release Notes voor 3.1.36-beta --- content/changelog/1.0/1.0.0.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/content/changelog/1.0/1.0.0.md b/content/changelog/1.0/1.0.0.md index bacfe0b..61abffc 100644 --- a/content/changelog/1.0/1.0.0.md +++ b/content/changelog/1.0/1.0.0.md @@ -5,9 +5,24 @@ All notable changes to EveAI will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 3.1.36-beta + +Release date: 2025-12-02 + +### Added +- Refactoring of the chat client to use tabs for the active conversation, history and settings. +- Introduction of shells for Mobile and Desktop clients, allowing for additional shells like plugins to be added in the future. + +### Fixed +- TRA-89—Problem solved where connection could get lost in sync between client and backend +- TRA-98—End user could continue without accepting dpa & terms +- TRA-96—Multiple-choice questions in the mobile client not scrolling → Solved by introducing a new client layout +- TRA-101—DPA-link was not working +- TRA-102—Wrong responses when looking for affirmative answers. + ## 3.1.26-beta -Release date: +Release date: 2025-11-26 ### Changed - Introduction of vueuse/core in the chat client, to ensure abstraction of ui behaviour for different mobile devices.