- Start met Mobiele versie van de chat client.

This commit is contained in:
Josako
2025-08-02 17:27:20 +02:00
parent 9a88582fff
commit 4d6466038f
11 changed files with 827 additions and 26 deletions

View File

@@ -18,6 +18,16 @@
width: 100%; width: 100%;
} }
/* Mobile header container - hidden on desktop */
#mobile-header-container {
display: none;
}
/* Desktop layout - default */
#sidebar-container {
display: block;
}
/* Sidebar styling */ /* Sidebar styling */
.sidebar { .sidebar {
width: 300px; width: 300px;
@@ -254,4 +264,29 @@ body {
/* .btn-primary wordt nu gedefinieerd in chat-components.css */ /* .btn-primary wordt nu gedefinieerd in chat-components.css */
/* Mobile responsive layout */
@media (max-width: 768px) {
/* Switch to vertical layout */
.app-container {
flex-direction: column;
}
/* Hide desktop sidebar on mobile */
#sidebar-container {
display: none;
}
/* Show mobile header on mobile */
#mobile-header-container {
display: block;
width: 100%;
}
/* Content area takes remaining space */
.content-area {
flex: 1;
height: calc(100vh - 60px); /* Subtract mobile header height */
}
}
/* Responsieve design regels worden nu gedefinieerd in chat-components.css */ /* Responsieve design regels worden nu gedefinieerd in chat-components.css */

View File

@@ -225,4 +225,22 @@ export default {
color: white; color: white;
padding: 8px; padding: 8px;
} }
/* Mobile-specific styling when used in mobile header */
@media (max-width: 768px) {
.language-selector {
margin: 0;
}
.language-selector label {
display: none; /* Hide label in mobile header */
}
.language-select {
padding: 6px 10px;
font-size: 0.85rem;
min-width: 120px;
margin: 0;
}
}
</style> </style>

View File

@@ -0,0 +1,146 @@
<!-- MobileHeader.vue -->
<template>
<div class="mobile-header">
<SideBarLogo
:logo-url="tenantMake.logo_url"
:make-name="tenantMake.name"
class="mobile-logo"
/>
<LanguageSelector
:initial-language="initialLanguage"
:current-language="currentLanguage"
:supported-language-details="supportedLanguageDetails"
:allowed-languages="allowedLanguages"
@language-changed="handleLanguageChange"
class="mobile-language-selector"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import SideBarLogo from './SideBarLogo.vue';
import LanguageSelector from './LanguageSelector.vue';
const props = defineProps({
tenantMake: {
type: Object,
default: () => ({
name: '',
logo_url: '',
subtitle: ''
})
},
initialLanguage: {
type: String,
default: 'en'
},
supportedLanguageDetails: {
type: Object,
default: () => ({})
},
allowedLanguages: {
type: Array,
default: () => ['nl', 'en', 'fr', 'de']
},
apiPrefix: {
type: String,
default: ''
}
});
const emit = defineEmits(['language-changed']);
const currentLanguage = ref(props.initialLanguage);
const handleLanguageChange = (newLanguage) => {
currentLanguage.value = newLanguage;
// Emit to parent
emit('language-changed', newLanguage);
// Global event for backward compatibility
const globalEvent = new CustomEvent('language-changed', {
detail: { language: newLanguage }
});
document.dispatchEvent(globalEvent);
// Update chatConfig
if (window.chatConfig) {
window.chatConfig.language = newLanguage;
}
// Save preference
localStorage.setItem('preferredLanguage', newLanguage);
};
</script>
<style scoped>
.mobile-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background: var(--sidebar-background);
color: var(--sidebar-color);
border-bottom: 1px solid rgba(0,0,0,0.1);
min-height: 60px;
}
/* Mobile logo styling - compact version */
.mobile-header :deep(.mobile-logo) {
flex-shrink: 0;
}
.mobile-header :deep(.mobile-logo .sidebar-logo) {
padding: 0;
margin-right: 15px;
}
.mobile-header :deep(.mobile-logo .logo-image) {
max-height: 40px;
max-width: 120px;
}
.mobile-header :deep(.mobile-logo .logo-placeholder) {
width: 40px;
height: 40px;
font-size: 1rem;
}
/* Mobile language selector styling - compact horizontal version */
.mobile-header :deep(.mobile-language-selector) {
flex-shrink: 0;
min-width: 140px;
}
.mobile-header :deep(.mobile-language-selector .language-selector) {
margin: 0;
}
.mobile-header :deep(.mobile-language-selector label) {
display: none; /* Hide label in mobile header */
}
.mobile-header :deep(.mobile-language-selector .language-select) {
padding: 6px 10px;
font-size: 0.85rem;
min-width: 120px;
margin: 0;
}
/* Show mobile header on mobile */
@media (max-width: 768px) {
.mobile-header {
display: flex;
}
}
/* Hide mobile header on desktop */
@media (min-width: 769px) {
.mobile-header {
display: none;
}
}
</style>

View File

@@ -69,4 +69,22 @@ const handleImageError = () => {
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.2rem;
} }
/* Mobile-specific styling when used in mobile header */
@media (max-width: 768px) {
.sidebar-logo {
padding: 5px 0; /* Reduce padding for mobile header */
}
.logo-image {
max-height: 40px; /* Smaller logo for mobile header */
max-width: 120px;
}
.logo-placeholder {
width: 40px;
height: 40px;
font-size: 1rem;
}
}
</style> </style>

View File

@@ -10,6 +10,8 @@ export { default as ProgressTracker } from './ProgressTracker.vue';
export { default as DynamicForm } from './DynamicForm.vue'; export { default as DynamicForm } from './DynamicForm.vue';
export { default as FormField } from './FormField.vue'; export { default as FormField } from './FormField.vue';
export { default as FormMessage } from './FormMessage.vue'; export { default as FormMessage } from './FormMessage.vue';
export { default as SideBar } from './SideBar.vue';
export { default as MobileHeader } from './MobileHeader.vue';
// Log successful loading // Log successful loading
console.log('Vue components loaded successfully from barrel export'); console.log('Vue components loaded successfully from barrel export');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,10 @@
</head> </head>
<body> <body>
<div id="app" class="app-container" data-vue-app="true"> <div id="app" class="app-container" data-vue-app="true">
<!-- Left sidebar - Vue component container --> <!-- Mobile header - Vue component container (hidden on desktop) -->
<div id="mobile-header-container"></div>
<!-- Left sidebar - Vue component container (hidden on mobile) -->
<div id="sidebar-container"></div> <div id="sidebar-container"></div>
<!-- Right content area - contains the chat client --> <!-- Right content area - contains the chat client -->

View File

@@ -21,48 +21,77 @@ window.Vue = { createApp };
window.marked = marked; window.marked = marked;
// Gebruik barrel export voor componenten // Gebruik barrel export voor componenten
import * as Components from '../../../eveai_chat_client/static/assets/js/components/index.js'; import * as Components from '../../../eveai_chat_client/static/assets/vue-components/index.js';
// Maak Components globaal beschikbaar voor debugging // Maak Components globaal beschikbaar voor debugging
window.Components = Components; window.Components = Components;
console.log('Components loaded:', Object.keys(Components)); console.log('Components loaded:', Object.keys(Components));
// Main chat application - moet als laatste worden geladen // Main chat application - moet als laatste worden geladen
import { ChatApp } from '../../../eveai_chat_client/static/assets/js/ChatApp.js'; import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue';
// Wacht tot het document volledig is geladen voordat we Vue initialiseren // Wacht tot het document volledig is geladen voordat we Vue initialiseren
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('Initializing Chat Application'); console.log('Initializing Chat Application');
// Check of #app element bestaat
const appElement = document.getElementById('app');
if (!appElement) {
console.error('DOM element #app not found. Cannot initialize Vue application.');
return;
}
console.log('DOM element #app exists:', appElement);
try { try {
// Maak de Vue applicatie aan // Mount SideBar component to sidebar-container
const app = createApp(ChatApp); const sidebarContainer = document.getElementById('sidebar-container');
if (sidebarContainer && Components.SideBar) {
const sidebarApp = createApp(Components.SideBar, {
tenantMake: window.chatConfig?.tenantMake || { name: 'EveAI', logo_url: '' },
explanationText: window.chatConfig?.explanationText || '',
initialLanguage: window.chatConfig?.language || 'en',
supportedLanguageDetails: window.chatConfig?.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig?.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig?.apiPrefix || ''
});
// Registreer alle componenten globaal // Registreer componenten voor sidebar app
for (const [name, component] of Object.entries(Components)) { for (const [name, component] of Object.entries(Components)) {
app.component(name, component); sidebarApp.component(name, component);
} }
// Voeg de IconManagerMixin toe voor alle componenten sidebarApp.mount('#sidebar-container');
app.mixin(IconManagerMixin); console.log('SideBar mounted successfully');
}
// Mount de applicatie op #app // Mount MobileHeader component to mobile-header-container
const mountedApp = app.mount('#app'); const mobileHeaderContainer = document.getElementById('mobile-header-container');
if (mobileHeaderContainer && Components.MobileHeader) {
const mobileHeaderApp = createApp(Components.MobileHeader, {
tenantMake: window.chatConfig?.tenantMake || { name: 'EveAI', logo_url: '' },
initialLanguage: window.chatConfig?.language || 'en',
supportedLanguageDetails: window.chatConfig?.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig?.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig?.apiPrefix || ''
});
// Bewaar een referentie naar de app voor debugging // Registreer componenten voor mobile header app
window.__vueApp = app; for (const [name, component] of Object.entries(Components)) {
mobileHeaderApp.component(name, component);
}
console.log('Vue app mounted successfully'); mobileHeaderApp.mount('#mobile-header-container');
console.log('MobileHeader mounted successfully');
}
// Mount ChatApp to the chat container
const chatContainer = document.querySelector('.chat-container');
if (chatContainer) {
const chatApp = createApp(ChatApp);
// Registreer alle componenten globaal voor chat app
for (const [name, component] of Object.entries(Components)) {
chatApp.component(name, component);
}
chatApp.mount('.chat-container');
console.log('ChatApp mounted successfully');
}
console.log('All Vue apps mounted successfully');
} catch (error) { } catch (error) {
console.error('Error initializing Vue application:', error); console.error('Error initializing Vue applications:', error);
} }
}); });

206
test_mobile_header_fix.py Normal file
View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""
Test script to verify mobile header visibility fix for eveai_chat_client
"""
import os
import sys
import re
def test_mobile_header_css_fix():
"""Test that MobileHeader component has correct CSS media queries"""
print("Testing MobileHeader CSS fix...")
mobile_header_path = 'eveai_chat_client/static/assets/vue-components/MobileHeader.vue'
if not os.path.exists(mobile_header_path):
print(f"❌ MobileHeader.vue not found at {mobile_header_path}")
return False
with open(mobile_header_path, 'r') as f:
content = f.read()
# Check for mobile media query (show on mobile)
mobile_show_pattern = r'@media\s*\(\s*max-width:\s*768px\s*\)\s*\{[^}]*\.mobile-header\s*\{[^}]*display:\s*flex'
mobile_show_match = re.search(mobile_show_pattern, content, re.DOTALL)
if not mobile_show_match:
print("❌ Missing mobile media query to show mobile header")
print(" Expected: @media (max-width: 768px) with .mobile-header { display: flex }")
return False
else:
print("✓ Found mobile media query to show header on mobile devices")
# Check for desktop media query (hide on desktop)
desktop_hide_pattern = r'@media\s*\(\s*min-width:\s*769px\s*\)\s*\{[^}]*\.mobile-header\s*\{[^}]*display:\s*none'
desktop_hide_match = re.search(desktop_hide_pattern, content, re.DOTALL)
if not desktop_hide_match:
print("❌ Missing desktop media query to hide mobile header")
print(" Expected: @media (min-width: 769px) with .mobile-header { display: none }")
return False
else:
print("✓ Found desktop media query to hide header on desktop devices")
# Check that both media queries exist in correct order
mobile_pos = content.find('@media (max-width: 768px)')
desktop_pos = content.find('@media (min-width: 769px)')
if mobile_pos == -1 or desktop_pos == -1:
print("❌ Media queries not found in expected format")
return False
if mobile_pos > desktop_pos:
print("⚠️ Warning: Mobile media query comes after desktop query (may cause specificity issues)")
print("✅ MobileHeader CSS fix is correctly implemented")
return True
def test_global_css_rules():
"""Test that global CSS rules for mobile-header-container are correct"""
print("\nTesting global CSS rules...")
css_path = 'eveai_chat_client/static/assets/css/chat.css'
if not os.path.exists(css_path):
print(f"❌ chat.css not found at {css_path}")
return False
with open(css_path, 'r') as f:
content = f.read()
# Check default hidden state
if '#mobile-header-container' not in content or 'display: none' not in content:
print("❌ Missing default hidden state for mobile-header-container")
return False
else:
print("✓ Found default hidden state for mobile-header-container")
# Check mobile media query shows container
# Look for the media query and then check if mobile-header-container is set to display: block within it
media_query_start = content.find('@media (max-width: 768px)')
if media_query_start == -1:
print("❌ Missing @media (max-width: 768px) query")
return False
# Find the closing brace of this media query
brace_count = 0
media_query_end = media_query_start
for i, char in enumerate(content[media_query_start:]):
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
media_query_end = media_query_start + i
break
media_query_content = content[media_query_start:media_query_end + 1]
if '#mobile-header-container' in media_query_content and 'display: block' in media_query_content:
print("✓ Found mobile media query to show mobile-header-container")
else:
print("❌ Mobile media query doesn't properly show mobile-header-container")
return False
print("✅ Global CSS rules are correct")
return True
def test_built_assets():
"""Test that built assets exist and are recent"""
print("\nTesting built assets...")
assets_to_check = [
'eveai_chat_client/static/dist/chat-client.js',
'eveai_chat_client/static/dist/chat-client.css'
]
for asset_path in assets_to_check:
if not os.path.exists(asset_path):
print(f"❌ Built asset not found: {asset_path}")
return False
else:
# Check file size to ensure it's not empty
size = os.path.getsize(asset_path)
if size == 0:
print(f"❌ Built asset is empty: {asset_path}")
return False
else:
print(f"✓ Built asset exists and has content: {asset_path} ({size} bytes)")
print("✅ Built assets are present and valid")
return True
def test_component_structure():
"""Test that MobileHeader component structure is intact"""
print("\nTesting MobileHeader component structure...")
mobile_header_path = 'eveai_chat_client/static/assets/vue-components/MobileHeader.vue'
with open(mobile_header_path, 'r') as f:
content = f.read()
required_elements = [
'class="mobile-header"',
'SideBarLogo',
'LanguageSelector',
'justify-content: space-between',
'align-items: center'
]
missing_elements = []
for element in required_elements:
if element not in content:
missing_elements.append(element)
else:
print(f"✓ Found: {element}")
if missing_elements:
print(f"❌ Missing elements in MobileHeader:")
for element in missing_elements:
print(f" - {element}")
return False
print("✅ MobileHeader component structure is intact")
return True
def main():
"""Run all tests"""
print("🔧 Testing Mobile Header Visibility Fix")
print("=" * 50)
tests = [
test_mobile_header_css_fix,
test_global_css_rules,
test_built_assets,
test_component_structure
]
passed = 0
failed = 0
for test in tests:
try:
if test():
passed += 1
else:
failed += 1
except Exception as e:
print(f"❌ Test failed with error: {e}")
failed += 1
print()
print("=" * 50)
print(f"📊 Test Results: {passed} passed, {failed} failed")
if failed == 0:
print("🎉 Mobile header visibility fix is complete!")
print("📱 The MobileHeader should now be visible when switching to mobile mode.")
return True
else:
print("⚠️ Some tests failed. Please review the implementation.")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)

257
test_mobile_responsive.py Normal file
View File

@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""
Test script to verify mobile responsive functionality for eveai_chat_client
"""
import os
import sys
def test_files_exist():
"""Test that all required files exist"""
print("Testing file existence...")
files_to_check = [
# Vue components
'eveai_chat_client/static/assets/vue-components/MobileHeader.vue',
'eveai_chat_client/static/assets/vue-components/SideBar.vue',
'eveai_chat_client/static/assets/vue-components/index.js',
# Templates
'eveai_chat_client/templates/base.html',
'eveai_chat_client/templates/scripts.html',
# CSS
'eveai_chat_client/static/assets/css/chat.css',
# Source files
'frontend_src/js/chat-client.js',
# Built files
'eveai_chat_client/static/dist/chat-client.js',
'eveai_chat_client/static/dist/chat-client.css',
]
missing_files = []
for file_path in files_to_check:
if not os.path.exists(file_path):
missing_files.append(file_path)
else:
print(f"{file_path}")
if missing_files:
print(f"\n❌ Missing files:")
for file_path in missing_files:
print(f" - {file_path}")
return False
print("✅ All required files exist")
return True
def test_mobile_header_component():
"""Test MobileHeader component structure"""
print("\nTesting MobileHeader component...")
mobile_header_path = 'eveai_chat_client/static/assets/vue-components/MobileHeader.vue'
with open(mobile_header_path, 'r') as f:
content = f.read()
# Check for required elements
required_elements = [
'class="mobile-header"',
'SideBarLogo',
'LanguageSelector',
'@media (min-width: 769px)',
'display: none'
]
missing_elements = []
for element in required_elements:
if element not in content:
missing_elements.append(element)
else:
print(f"✓ Found: {element}")
if missing_elements:
print(f"❌ Missing elements in MobileHeader:")
for element in missing_elements:
print(f" - {element}")
return False
print("✅ MobileHeader component structure is correct")
return True
def test_css_responsive_rules():
"""Test CSS responsive rules"""
print("\nTesting CSS responsive rules...")
css_path = 'eveai_chat_client/static/assets/css/chat.css'
with open(css_path, 'r') as f:
content = f.read()
# Check for required CSS rules
required_rules = [
'#mobile-header-container',
'#sidebar-container',
'@media (max-width: 768px)',
'flex-direction: column',
'display: none',
'display: block'
]
missing_rules = []
for rule in required_rules:
if rule not in content:
missing_rules.append(rule)
else:
print(f"✓ Found: {rule}")
if missing_rules:
print(f"❌ Missing CSS rules:")
for rule in missing_rules:
print(f" - {rule}")
return False
print("✅ CSS responsive rules are correct")
return True
def test_base_html_structure():
"""Test base.html structure"""
print("\nTesting base.html structure...")
base_html_path = 'eveai_chat_client/templates/base.html'
with open(base_html_path, 'r') as f:
content = f.read()
# Check for required elements
required_elements = [
'id="mobile-header-container"',
'id="sidebar-container"',
'class="content-area"'
]
missing_elements = []
for element in required_elements:
if element not in content:
missing_elements.append(element)
else:
print(f"✓ Found: {element}")
if missing_elements:
print(f"❌ Missing elements in base.html:")
for element in missing_elements:
print(f" - {element}")
return False
print("✅ base.html structure is correct")
return True
def test_component_exports():
"""Test component exports"""
print("\nTesting component exports...")
index_js_path = 'eveai_chat_client/static/assets/vue-components/index.js'
with open(index_js_path, 'r') as f:
content = f.read()
# Check for required exports
required_exports = [
'export { default as SideBar }',
'export { default as MobileHeader }'
]
missing_exports = []
for export in required_exports:
if export not in content:
missing_exports.append(export)
else:
print(f"✓ Found: {export}")
if missing_exports:
print(f"❌ Missing exports:")
for export in missing_exports:
print(f" - {export}")
return False
print("✅ Component exports are correct")
return True
def test_chat_client_js():
"""Test chat-client.js mounting logic"""
print("\nTesting chat-client.js mounting logic...")
chat_client_path = 'frontend_src/js/chat-client.js'
with open(chat_client_path, 'r') as f:
content = f.read()
# Check for required mounting logic
required_elements = [
'getElementById(\'sidebar-container\')',
'getElementById(\'mobile-header-container\')',
'Components.SideBar',
'Components.MobileHeader',
'mount(\'#sidebar-container\')',
'mount(\'#mobile-header-container\')'
]
missing_elements = []
for element in required_elements:
if element not in content:
missing_elements.append(element)
else:
print(f"✓ Found: {element}")
if missing_elements:
print(f"❌ Missing elements in chat-client.js:")
for element in missing_elements:
print(f" - {element}")
return False
print("✅ chat-client.js mounting logic is correct")
return True
def main():
"""Run all tests"""
print("🧪 Testing Mobile Responsive Implementation")
print("=" * 50)
tests = [
test_files_exist,
test_mobile_header_component,
test_css_responsive_rules,
test_base_html_structure,
test_component_exports,
test_chat_client_js
]
passed = 0
failed = 0
for test in tests:
try:
if test():
passed += 1
else:
failed += 1
except Exception as e:
print(f"❌ Test failed with error: {e}")
failed += 1
print()
print("=" * 50)
print(f"📊 Test Results: {passed} passed, {failed} failed")
if failed == 0:
print("🎉 All tests passed! Mobile responsive implementation is complete.")
return True
else:
print("⚠️ Some tests failed. Please review the implementation.")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)