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..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.f8ee4d5a.js",
- "dist/chat-client.css": "dist/chat-client.2fffefae.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 6a4aa45..deac7c4 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
-
-
+
+
+
-
-
+
+
+
+
+
768,
+ // Mobile tab state
+ activeTabId: 'chat',
+
// Advanced features
messageSearch: '',
filteredMessages: [],
@@ -193,16 +271,57 @@ export default {
return this.supportedLanguages.filter(lang =>
this.allowedLanguages.includes(lang.code)
);
+ },
+
+ mobileTabs() {
+ return [
+ {
+ id: 'chat',
+ iconName: 'chat',
+ label: 'Chat'
+ },
+ {
+ id: 'history',
+ iconName: 'history',
+ label: 'Historiek'
+ },
+ {
+ id: 'setup',
+ iconName: 'settings',
+ label: 'Setup'
+ }
+ ];
}
},
mounted() {
this.initializeChat();
this.setupEventListeners();
+
+ // Stel initiale actieve tab in (optioneel via config)
+ const defaultTab = (window.chatConfig && window.chatConfig.defaultTab) || 'chat';
+ if (this.mobileTabs.find(t => t.id === defaultTab)) {
+ this.activeTabId = defaultTab;
+ }
+
+ // Luister naar globale events om tab te wisselen
+ this.globalTabListener = (event) => {
+ const tabId = event?.detail?.tabId;
+ if (!tabId) return;
+ if (this.mobileTabs.find(t => t.id === tabId)) {
+ this.activeTabId = tabId;
+ }
+ };
+
+ document.addEventListener('evie-chat-set-tab', this.globalTabListener);
},
beforeUnmount() {
this.cleanup();
+
+ if (this.globalTabListener) {
+ document.removeEventListener('evie-chat-set-tab', this.globalTabListener);
+ }
},
methods: {
@@ -453,11 +572,13 @@ export default {
}
});
- // Window resize listener
- window.addEventListener('resize', () => {
- this.isMobile = window.innerWidth <= 768;
+ // Window resize listener voor fallback flags
+ this.handleResize = () => {
+ this.isMobileFallback = window.innerWidth <= 768;
this.showSidebar = window.innerWidth > 768;
- });
+ };
+
+ window.addEventListener('resize', this.handleResize);
},
cleanup() {
@@ -516,6 +637,12 @@ export default {
this.isLoading = false;
},
+ handleLanguageChangedFromSetup(newLanguage) {
+ // Update lokale taalstate; verdere effecten worden opgepikt door
+ // bestaande global listener en LanguageProvider / chatConfig.
+ this.currentLanguage = newLanguage;
+ },
+
// UI helpers
scrollToBottom() {
if (this.$refs.messageHistory) {
@@ -560,7 +687,7 @@ export default {
display: flex;
flex-direction: column;
flex: 1;
- /* height: 100%; avoided to let flex sizing control height */
+ height: 100%;
width: 100%;
min-height: 0;
max-width: 1000px;
@@ -571,6 +698,84 @@ export default {
overflow: hidden;
}
+.chat-mobile-layout {
+ display: flex;
+ flex-direction: column;
+ /* Binnen SafeViewport vertrouwen we op de hoogte van de bovenliggende
+ containers (html/body/app-container). Deze layout moet zich daaraan
+ aanpassen en niet opnieuw zelf een safe-vh berekening doen, om
+ dubbele afrondingsfouten en extra scrollruimte te vermijden. */
+ flex: 1 1 auto;
+ height: 100%;
+ min-height: 0;
+}
+
+.chat-mobile-content {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Mobiele header met logo links en tabs rechts */
+.chat-mobile-header {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ justify-content: space-between;
+ padding: 6px 8px;
+ background: var(--tab-background, #0a0a0a);
+}
+
+.chat-mobile-header-left {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ min-width: 56px; /* Zorg dat logo altijd minstens vierkant kan tonen */
+ margin-right: 4px;
+}
+
+.chat-mobile-header-right {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ overflow: hidden;
+}
+
+/* Specifieke layout voor de chat-tab: inputblok onderaan */
+.chat-mobile-content.tab-chat {
+ display: flex;
+ flex-direction: column;
+}
+
+.tab-chat-input {
+ margin-top: auto;
+ flex-shrink: 0;
+ /* Iets meer visuele marge boven de onderrand, maar nog steeds rekening houden met safe inset */
+ padding-bottom: calc(6px + var(--safe-bottom-inset, 0px));
+}
+
+/* Wanneer het toetsenbord open is (gedetecteerd door useChatViewport via
+ de body-klasse chat-keyboard-open), willen we geen extra grote
+ safe-bottom-inset meer onder de input. Dan sluiten we zo veel mogelijk
+ aan tegen de visuele viewport en houden we alleen een kleine vaste marge. */
+body.chat-keyboard-open .tab-chat-input {
+ padding-bottom: 6px;
+}
+
+@media (max-width: 768px) {
+ .chat-app-container {
+ /* Minder padding op mobiel zodat de tabbar binnen de viewport valt */
+ padding: 8px 8px 0 8px;
+ }
+}
+
.chat-messages-area {
flex: 1;
min-height: 0; /* ensure child can scroll */
diff --git a/eveai_chat_client/static/assets/vue-components/ChatInput.vue b/eveai_chat_client/static/assets/vue-components/ChatInput.vue
index 79072fd..f22d08f 100644
--- a/eveai_chat_client/static/assets/vue-components/ChatInput.vue
+++ b/eveai_chat_client/static/assets/vue-components/ChatInput.vue
@@ -452,6 +452,7 @@ export default {
transition: opacity 0.2s ease-in-out;
margin-left: auto;
margin-right: auto;
+ margin-bottom: 20px;
}
/* Input veld en knoppen */
diff --git a/eveai_chat_client/static/assets/vue-components/ChatMessage.vue b/eveai_chat_client/static/assets/vue-components/ChatMessage.vue
index fe58241..2c5e45c 100644
--- a/eveai_chat_client/static/assets/vue-components/ChatMessage.vue
+++ b/eveai_chat_client/static/assets/vue-components/ChatMessage.vue
@@ -784,21 +784,32 @@ export default {
}
}
-/* Bubble height constraints and inner scroll containment (apply on all viewports) */
+/* Bubble height constraints en inner scroll containment.
+ Fallback gebruikt klassieke vh-units; de @supports-blok hieronder
+ schakelt over naar SafeViewport via var(--safe-vh) wanneer mogelijk. */
.message .message-content {
- max-height: 33vh; /* fallback */
- overflow-y: auto;
- overscroll-behavior: contain; /* prevent scroll chaining to parent */
- -webkit-overflow-scrolling: touch; /* iOS smooth inertia */
+ max-height: 33vh; /* fallback */
+ overflow-y: auto;
+ overscroll-behavior: contain; /* prevent scroll chaining to parent */
+ -webkit-overflow-scrolling: touch; /* iOS smooth inertia */
}
-/* Active contexts (input area or sticky area): allow up to half viewport */
+
+/* Active contexts (input area of sticky area): mogen meer hoogte innemen */
.message.input-area .message-content,
.message.sticky-area .message-content {
- max-height: 50vh; /* fallback */
+ max-height: 50vh; /* fallback */
}
+
@supports (max-height: 1svh) {
- .message .message-content { max-height: 33svh; }
- .message.input-area .message-content,
- .message.sticky-area .message-content { max-height: 50svh; }
+ .message .message-content {
+ /* Gebruik veilige viewporthoogte die door useChatViewport gezet wordt */
+ max-height: calc(var(--safe-vh, 1vh) * 33);
+ }
+
+ .message.input-area .message-content,
+ .message.sticky-area .message-content {
+ /* In de input-/sticky-area mag de bubbel ruimer zijn */
+ max-height: calc(var(--safe-vh, 1vh) * 60);
+ }
}
\ No newline at end of file
diff --git a/eveai_chat_client/static/assets/vue-components/MobileHeader.vue b/eveai_chat_client/static/assets/vue-components/MobileHeader.vue
index 3a200ba..2f4a4a0 100644
--- a/eveai_chat_client/static/assets/vue-components/MobileHeader.vue
+++ b/eveai_chat_client/static/assets/vue-components/MobileHeader.vue
@@ -5,22 +5,11 @@
:make-name="tenantMake.name"
class="mobile-logo"
/>
-
-
diff --git a/eveai_chat_client/static/assets/vue-components/SafeViewport.vue b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue
index 79d63c2..e1b3513 100644
--- a/eveai_chat_client/static/assets/vue-components/SafeViewport.vue
+++ b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue
@@ -24,5 +24,8 @@ useChatViewport();
height: 100%;
display: flex;
flex-direction: column;
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: hidden;
}
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)