Changes for eveai_chat_client:
- Modal display of privacy statement & Terms & Conditions - Consent-flag ==> check of privacy and Terms & Conditions - customisation option added to show or hide DynamicForm titles
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
# Kubernetes Logging Upgrade
|
||||
|
||||
## Overzicht
|
||||
Deze instructies beschrijven hoe je alle services moet bijwerken om de nieuwe logging configuratie te gebruiken die zowel compatibel is met traditionele bestandsgebaseerde logging (voor ontwikkeling/test) als met Kubernetes (voor productie).
|
||||
|
||||
## Stappen voor elke service
|
||||
|
||||
Pas de volgende wijzigingen toe in elk van de volgende services:
|
||||
|
||||
- eveai_app
|
||||
- eveai_workers
|
||||
- eveai_api
|
||||
- eveai_chat_client
|
||||
- eveai_chat_workers
|
||||
- eveai_beat
|
||||
- eveai_entitlements
|
||||
|
||||
### 1. Update de imports
|
||||
|
||||
Verander:
|
||||
```python
|
||||
from config.logging_config import LOGGING
|
||||
```
|
||||
|
||||
Naar:
|
||||
```python
|
||||
from config.logging_config import configure_logging
|
||||
```
|
||||
|
||||
### 2. Update de logging configuratie
|
||||
|
||||
Verander:
|
||||
```python
|
||||
logging.config.dictConfig(LOGGING)
|
||||
```
|
||||
|
||||
Naar:
|
||||
```python
|
||||
configure_logging()
|
||||
```
|
||||
|
||||
## Dockerfile Aanpassingen
|
||||
|
||||
Voeg de volgende regels toe aan je Dockerfile voor elke service om de Kubernetes-specifieke logging afhankelijkheden te installeren (alleen voor productie):
|
||||
|
||||
```dockerfile
|
||||
# Alleen voor productie (Kubernetes) builds
|
||||
COPY requirements-k8s.txt /app/
|
||||
RUN if [ "$ENVIRONMENT" = "production" ]; then pip install -r requirements-k8s.txt; fi
|
||||
```
|
||||
|
||||
## Kubernetes Deployment
|
||||
|
||||
Zorg ervoor dat je Kubernetes deployment manifests de volgende omgevingsvariabele bevatten:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: FLASK_ENV
|
||||
value: "production"
|
||||
```
|
||||
|
||||
## Voordelen
|
||||
|
||||
1. De code detecteert automatisch of deze in Kubernetes draait
|
||||
2. In ontwikkeling/test omgevingen blijft alles naar bestanden schrijven
|
||||
3. In Kubernetes gaan logs naar stdout/stderr in JSON-formaat
|
||||
4. Geen wijzigingen nodig in bestaande logger code in de applicatie
|
||||
@@ -1,112 +0,0 @@
|
||||
# Vue 3 Component Refactoring Pattern
|
||||
|
||||
## Successfully Applied to: LanguageSelector ✅
|
||||
|
||||
This document outlines the pattern for refactoring Vue components from the problematic `renderComponent()` approach to proper Vue 3 templates.
|
||||
|
||||
## The Problem
|
||||
|
||||
Components were using both Vue templates AND manual DOM manipulation via `renderComponent()` methods, which caused conflicts with Vue's reactivity system.
|
||||
|
||||
## The Solution Pattern
|
||||
|
||||
### 1. Component File Changes
|
||||
|
||||
**Remove these problematic elements:**
|
||||
- `renderComponent()` method (manual DOM manipulation)
|
||||
- `render()` fallback method
|
||||
- Call to `this.renderComponent()` in `mounted()` lifecycle
|
||||
|
||||
**Keep these Vue elements:**
|
||||
- `template:` with proper Vue directives (v-model, @change, v-for, etc.)
|
||||
- `name`, `props`, `data()`, `methods`, `mounted()` structure
|
||||
- Vue event handling (`$emit`, `@change`)
|
||||
|
||||
### 2. Chat-client.js Changes
|
||||
|
||||
**Remove fallback logic:**
|
||||
- Remove try/catch blocks that attempt `renderComponent()` as fallback
|
||||
- Remove manual DOM manipulation fallbacks
|
||||
- Keep only clean Vue mounting: `app.mount(container)`
|
||||
|
||||
### 3. Testing Pattern
|
||||
|
||||
**Validation steps:**
|
||||
1. Build project successfully (`npm run build`)
|
||||
2. Verify renderComponent() methods removed
|
||||
3. Verify Vue template structure intact
|
||||
4. Verify fallback logic removed from chat-client.js
|
||||
5. Test component functionality in browser
|
||||
|
||||
## Example: LanguageSelector Before/After
|
||||
|
||||
### Before (Problematic):
|
||||
```javascript
|
||||
mounted() {
|
||||
this.renderComponent(); // ❌ Manual DOM manipulation
|
||||
this.$emit('language-changed', this.selectedLanguage);
|
||||
},
|
||||
methods: {
|
||||
renderComponent() { // ❌ Manual DOM manipulation
|
||||
const container = document.getElementById('language-selector-container');
|
||||
container.innerHTML = `...`; // Direct DOM manipulation
|
||||
}
|
||||
},
|
||||
template: `...`, // ✅ Vue template (but overridden by renderComponent)
|
||||
render() { // ❌ Fallback method
|
||||
return document.createElement('div');
|
||||
}
|
||||
```
|
||||
|
||||
### After (Clean Vue 3):
|
||||
```javascript
|
||||
mounted() {
|
||||
// ✅ Only Vue lifecycle logic
|
||||
this.$emit('language-changed', this.selectedLanguage);
|
||||
},
|
||||
methods: {
|
||||
changeLanguage(languageCode) {
|
||||
// ✅ Only Vue reactive logic
|
||||
this.selectedLanguage = languageCode;
|
||||
this.$emit('language-changed', languageCode);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="language-selector">
|
||||
<select v-model="selectedLanguage" @change="changeLanguage(selectedLanguage)">
|
||||
<option v-for="lang in getAvailableLanguages()" :key="lang.code" :value="lang.code">
|
||||
{{ lang.flag }} {{ lang.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
` // ✅ Clean Vue template with reactivity
|
||||
```
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
- ✅ Proper Vue 3 reactivity
|
||||
- ✅ Cleaner, maintainable code
|
||||
- ✅ Better debugging with Vue DevTools
|
||||
- ✅ No DOM manipulation conflicts
|
||||
- ✅ Modern Vue patterns
|
||||
- ✅ Successful build without errors
|
||||
|
||||
## Next Components to Refactor
|
||||
|
||||
Based on previous analysis, these components need the same treatment:
|
||||
- ChatInput.js
|
||||
- MessageHistory.js
|
||||
- ChatMessage.js
|
||||
- TypingIndicator.js
|
||||
- ProgressTracker.js
|
||||
- FormField.js
|
||||
- DynamicForm.js
|
||||
- ChatApp.js
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- ✅ Component builds without errors
|
||||
- ✅ No renderComponent() methods in codebase
|
||||
- ✅ No fallback logic in chat-client.js
|
||||
- ✅ Vue templates work with proper reactivity
|
||||
- ✅ All functionality preserved
|
||||
@@ -1,117 +0,0 @@
|
||||
# Vue 3 Component Refactoring Progress
|
||||
|
||||
## 🎯 Mission: Remove all renderComponent() methods and use pure Vue 3 templates
|
||||
|
||||
## ✅ Successfully Completed Components (3/8)
|
||||
|
||||
### 1. LanguageSelector.js ✅
|
||||
- **Status**: COMPLETED
|
||||
- **Changes Made**:
|
||||
- ❌ Removed `renderComponent()` method (lines 107-142)
|
||||
- ❌ Removed `render()` fallback method (lines 159-161)
|
||||
- ❌ Removed `this.renderComponent()` call from `mounted()`
|
||||
- ✅ Kept clean Vue template with `v-model` and `@change`
|
||||
- **Result**: Clean Vue 3 component with proper reactivity
|
||||
- **Build**: ✅ Successful
|
||||
- **Tests**: ✅ All passed
|
||||
|
||||
### 2. ChatInput.js ✅
|
||||
- **Status**: COMPLETED
|
||||
- **Changes Made**:
|
||||
- ❌ Removed `renderComponent(container, props, app)` method (lines 16-80)
|
||||
- ✅ Kept Vue template with dynamic form handling
|
||||
- ✅ Preserved all functionality (textarea, form validation, etc.)
|
||||
- **Result**: Clean Vue 3 component with complex form handling
|
||||
- **Build**: ✅ Successful
|
||||
- **Tests**: ✅ All passed
|
||||
|
||||
### 3. MessageHistory.js ✅
|
||||
- **Status**: COMPLETED
|
||||
- **Changes Made**:
|
||||
- ❌ Removed static `MessageHistory.renderComponent()` method (lines 232-252)
|
||||
- ✅ Kept Vue template with message rendering and typing indicator
|
||||
- ✅ Preserved scroll handling and message display logic
|
||||
- **Result**: Clean Vue 3 component with proper message display
|
||||
- **Build**: ✅ Successful
|
||||
|
||||
## 🔄 Remaining Components to Refactor (5/8)
|
||||
|
||||
### 4. ChatMessage.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 11
|
||||
- **Estimated Complexity**: Medium (individual message rendering)
|
||||
|
||||
### 5. TypingIndicator.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 27 (static method)
|
||||
- **Estimated Complexity**: Low (simple animation component)
|
||||
|
||||
### 6. ProgressTracker.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 314 (static method)
|
||||
- **Estimated Complexity**: Medium (progress visualization)
|
||||
|
||||
### 7. DynamicForm.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 4
|
||||
- **Estimated Complexity**: High (complex form handling)
|
||||
|
||||
### 8. FormField.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 4
|
||||
- **Estimated Complexity**: Medium (individual form field)
|
||||
|
||||
### 9. FormMessage.js
|
||||
- **Status**: PENDING
|
||||
- **renderComponent Location**: Line 6
|
||||
- **Estimated Complexity**: Low (message display)
|
||||
|
||||
## 📊 Overall Progress
|
||||
|
||||
- **Completed**: 3/8 components (37.5%)
|
||||
- **Remaining**: 5/8 components (62.5%)
|
||||
- **Build Status**: ✅ All builds successful
|
||||
- **Bundle Size**: Consistent at ~240KB (no functionality lost)
|
||||
|
||||
## 🏗️ Infrastructure Changes Completed
|
||||
|
||||
### chat-client.js Cleanup ✅
|
||||
- ❌ Removed all renderComponent() fallback logic
|
||||
- ❌ Removed manual DOM manipulation fallbacks
|
||||
- ✅ Kept only clean Vue mounting: `app.mount(container)`
|
||||
- ✅ Removed renderComponent-related debug logging
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Continue with ChatMessage.js** (simplest remaining component)
|
||||
2. **Then TypingIndicator.js** (low complexity)
|
||||
3. **Then FormMessage.js** (low complexity)
|
||||
4. **Then FormField.js** (medium complexity)
|
||||
5. **Then ProgressTracker.js** (medium complexity)
|
||||
6. **Finally DynamicForm.js** (highest complexity)
|
||||
|
||||
## 🔧 Established Pattern
|
||||
|
||||
For each component:
|
||||
1. ✅ Identify renderComponent() method location
|
||||
2. ✅ Verify Vue template exists and is functional
|
||||
3. ✅ Remove renderComponent() method completely
|
||||
4. ✅ Build project to test
|
||||
5. ✅ Validate with test script
|
||||
|
||||
## 🚀 Benefits Achieved So Far
|
||||
|
||||
- ✅ Proper Vue 3 reactivity for 3 core components
|
||||
- ✅ Cleaner, maintainable code
|
||||
- ✅ Better debugging with Vue DevTools
|
||||
- ✅ No DOM manipulation conflicts
|
||||
- ✅ Modern Vue patterns
|
||||
- ✅ Consistent successful builds
|
||||
- ✅ No functionality regression
|
||||
|
||||
## 🎉 Success Metrics
|
||||
|
||||
- **Build Success Rate**: 100% (6/6 builds successful)
|
||||
- **Test Pass Rate**: 100% (all validation tests passed)
|
||||
- **Code Reduction**: ~150+ lines of problematic code removed
|
||||
- **Bundle Stability**: No size increases or functionality loss
|
||||
@@ -1,235 +0,0 @@
|
||||
# SideBar Vue Component Migration - Complete Implementation Summary
|
||||
|
||||
## 🎯 Migration Overview
|
||||
|
||||
Successfully migrated the entire sidebar from static HTML to modern Vue 3 components, following the same architectural pattern as ChatApp.vue. This creates a consistent, maintainable, and future-proof sidebar system.
|
||||
|
||||
## 📁 Files Created
|
||||
|
||||
### Vue Components
|
||||
1. **SideBarLogo.vue** - Logo display with fallback initials
|
||||
2. **SideBarMakeName.vue** - Tenant make name and subtitle display
|
||||
3. **SideBarExplanation.vue** - Translatable explanation content with markdown support
|
||||
4. **SideBar.vue** - Main orchestrating component
|
||||
|
||||
### Test Files
|
||||
5. **test_sidebar_components.js** - Comprehensive test suite for sidebar functionality
|
||||
|
||||
## 🔧 Files Modified
|
||||
|
||||
### Templates
|
||||
- **base.html** - Replaced static sidebar HTML with `#sidebar-container`
|
||||
|
||||
### JavaScript
|
||||
- **nginx/frontend_src/js/chat-client.js** - Added SideBar import and replaced old functions with `initializeSidebar()`
|
||||
|
||||
## ✅ Implementation Details
|
||||
|
||||
### 1. SideBarLogo.vue
|
||||
```vue
|
||||
- Props: logoUrl, makeName
|
||||
- Features: Image error handling, initials fallback
|
||||
- Styling: Responsive logo display with placeholder
|
||||
```
|
||||
|
||||
### 2. SideBarMakeName.vue
|
||||
```vue
|
||||
- Props: makeName, subtitle
|
||||
- Features: Conditional subtitle display
|
||||
- Styling: Centered text with proper hierarchy
|
||||
```
|
||||
|
||||
### 3. SideBarExplanation.vue
|
||||
```vue
|
||||
- Props: originalText, currentLanguage, apiPrefix
|
||||
- Features:
|
||||
* Automatic translation on language change
|
||||
* Markdown rendering support
|
||||
* Loading states during translation
|
||||
* Error handling with fallbacks
|
||||
- Integration: Uses useTranslationClient composable
|
||||
```
|
||||
|
||||
### 4. SideBar.vue (Main Component)
|
||||
```vue
|
||||
- Props: tenantMake, explanationText, initialLanguage, etc.
|
||||
- Features:
|
||||
* Orchestrates all sub-components
|
||||
* Handles language change events
|
||||
* Maintains backward compatibility
|
||||
* Responsive design
|
||||
- Event Handling: Emits language-changed events globally
|
||||
```
|
||||
|
||||
## 🔄 Migration Changes
|
||||
|
||||
### Before (Static HTML)
|
||||
```html
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<img src="{{ tenant_make.logo_url }}" alt="{{ tenant_make.name }}">
|
||||
</div>
|
||||
<div class="sidebar-make-name">{{ tenant_make.name }}</div>
|
||||
<div id="language-selector-container"></div>
|
||||
<div class="sidebar-explanation" id="sidebar-explanation"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### After (Vue Component)
|
||||
```html
|
||||
<div id="sidebar-container"></div>
|
||||
```
|
||||
|
||||
### JavaScript Changes
|
||||
**Removed Functions:**
|
||||
- `fillSidebarExplanation()`
|
||||
- `initializeLanguageSelector()`
|
||||
|
||||
**Added Function:**
|
||||
- `initializeSidebar()` - Mounts complete SideBar Vue component
|
||||
|
||||
## 🎯 Key Features Achieved
|
||||
|
||||
### ✅ **Modern Vue 3 Architecture**
|
||||
- Composition API with `<script setup>`
|
||||
- Reactive props and computed properties
|
||||
- Proper component lifecycle management
|
||||
|
||||
### ✅ **Translation Integration**
|
||||
- Automatic sidebar explanation translation
|
||||
- Uses existing useTranslationClient composable
|
||||
- Maintains translation cache benefits
|
||||
- Loading states during translation
|
||||
|
||||
### ✅ **Backward Compatibility**
|
||||
- Global language-changed events maintained
|
||||
- ChatConfig integration preserved
|
||||
- Existing CSS variables supported
|
||||
|
||||
### ✅ **Error Handling**
|
||||
- Image loading fallbacks
|
||||
- Translation error recovery
|
||||
- Component mounting error handling
|
||||
- Graceful degradation
|
||||
|
||||
### ✅ **Responsive Design**
|
||||
- Mobile-friendly layout
|
||||
- Flexible component sizing
|
||||
- CSS variable integration
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test Coverage
|
||||
- Component availability verification
|
||||
- Props validation
|
||||
- Old sidebar cleanup confirmation
|
||||
- Translation API integration testing
|
||||
|
||||
### Test Functions Available
|
||||
```javascript
|
||||
window.testComponentAvailability()
|
||||
window.testSidebarProps()
|
||||
window.testOldSidebarCleanup()
|
||||
window.testTranslationIntegration()
|
||||
window.runAllSidebarTests()
|
||||
```
|
||||
|
||||
## 📋 Component Props Structure
|
||||
|
||||
### SideBar.vue Props
|
||||
```javascript
|
||||
{
|
||||
tenantMake: {
|
||||
name: string,
|
||||
logo_url: string,
|
||||
subtitle: string
|
||||
},
|
||||
explanationText: string,
|
||||
initialLanguage: string,
|
||||
supportedLanguageDetails: object,
|
||||
allowedLanguages: array,
|
||||
apiPrefix: string
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 Integration Points
|
||||
|
||||
### With ChatApp.vue
|
||||
- Consistent architectural pattern
|
||||
- Shared event system for language changes
|
||||
- Compatible CSS variable usage
|
||||
|
||||
### With Translation System
|
||||
- Direct useTranslationClient integration
|
||||
- Backend API communication
|
||||
- Cache utilization
|
||||
|
||||
### With Existing Infrastructure
|
||||
- Flask template data integration
|
||||
- CSS variable compatibility
|
||||
- Event system preservation
|
||||
|
||||
## 🚀 Benefits Achieved
|
||||
|
||||
### 🎯 **Development Benefits**
|
||||
1. **Consistent Architecture** - Matches ChatApp.vue pattern
|
||||
2. **Component Reusability** - Sub-components can be reused
|
||||
3. **Better Maintainability** - Clear separation of concerns
|
||||
4. **Hot Reload Support** - Faster development cycle
|
||||
5. **Vue DevTools Support** - Better debugging capabilities
|
||||
|
||||
### 🎯 **User Experience Benefits**
|
||||
1. **Automatic Translation** - Sidebar content translates with language changes
|
||||
2. **Loading Feedback** - Visual feedback during translations
|
||||
3. **Error Recovery** - Graceful fallbacks on failures
|
||||
4. **Responsive Design** - Better mobile experience
|
||||
5. **Consistent Styling** - Unified design system
|
||||
|
||||
### 🎯 **Technical Benefits**
|
||||
1. **Modern Vue 3** - Latest framework features
|
||||
2. **Reactive State** - Automatic UI updates
|
||||
3. **Component Isolation** - Scoped styling and logic
|
||||
4. **Event-Driven** - Clean component communication
|
||||
5. **Future-Proof** - Ready for further enhancements
|
||||
|
||||
## 📊 Migration Status
|
||||
|
||||
### ✅ **Completed**
|
||||
- [x] All Vue components created
|
||||
- [x] Template updated
|
||||
- [x] JavaScript initialization updated
|
||||
- [x] Translation integration working
|
||||
- [x] Test suite created
|
||||
- [x] Old code cleaned up
|
||||
- [x] Documentation completed
|
||||
|
||||
### 🎉 **Result**
|
||||
**FULLY FUNCTIONAL MODERN VUE SIDEBAR SYSTEM**
|
||||
|
||||
The sidebar is now a complete Vue 3 application that:
|
||||
- Automatically translates content when language changes
|
||||
- Provides consistent user experience
|
||||
- Follows modern development patterns
|
||||
- Maintains full backward compatibility
|
||||
- Is ready for future enhancements
|
||||
|
||||
## 🔧 Usage
|
||||
|
||||
### For Developers
|
||||
```javascript
|
||||
// The sidebar is automatically initialized in chat-client.js
|
||||
// No manual intervention required
|
||||
|
||||
// For testing:
|
||||
runAllSidebarTests()
|
||||
```
|
||||
|
||||
### For Future Enhancements
|
||||
- Add new sub-components to SideBar.vue
|
||||
- Extend translation contexts
|
||||
- Add animations/transitions
|
||||
- Implement additional responsive features
|
||||
|
||||
## 🎯 **MIGRATION COMPLETE AND SUCCESSFUL** ✅
|
||||
|
||||
The sidebar has been successfully modernized and is now fully integrated with the Vue 3 ecosystem, providing a solid foundation for future development while maintaining all existing functionality.
|
||||
@@ -29,6 +29,7 @@ def get_default_chat_customisation(tenant_customisation=None):
|
||||
'gradient_start_color': '#f5f7fa',
|
||||
'gradient_end_color': '#c3cfe2',
|
||||
'progress_tracker_insights': 'No Information',
|
||||
'form_title_display': 'Full Title',
|
||||
'active_background_color': '#ffffff',
|
||||
'active_text_color': '#212529',
|
||||
'history_background': 10,
|
||||
|
||||
@@ -4,8 +4,6 @@ import logging
|
||||
from packaging import version
|
||||
from flask import current_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ContentManager:
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
@@ -16,10 +14,10 @@ class ContentManager:
|
||||
self.app = app
|
||||
|
||||
# Controleer of het pad bestaat
|
||||
if not os.path.exists(app.config['CONTENT_DIR']):
|
||||
logger.warning(f"Content directory not found at: {app.config['CONTENT_DIR']}")
|
||||
else:
|
||||
logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
|
||||
# if not os.path.exists(app.config['CONTENT_DIR']):
|
||||
# logger.warning(f"Content directory not found at: {app.config['CONTENT_DIR']}")
|
||||
# else:
|
||||
# logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
|
||||
|
||||
def get_content_path(self, content_type, major_minor=None, patch=None):
|
||||
"""
|
||||
@@ -66,12 +64,12 @@ class ContentManager:
|
||||
content_path = os.path.join(self.app.config['CONTENT_DIR'], content_type)
|
||||
|
||||
if not os.path.exists(content_path):
|
||||
logger.error(f"Content path does not exist: {content_path}")
|
||||
current_app.logger.error(f"Content path does not exist: {content_path}")
|
||||
return None
|
||||
|
||||
# Als geen major_minor opgegeven, vind de hoogste
|
||||
if not major_minor:
|
||||
available_versions = os.listdir(content_path)
|
||||
available_versions = [f for f in os.listdir(content_path) if not f.startswith('.')]
|
||||
if not available_versions:
|
||||
return None
|
||||
|
||||
@@ -81,16 +79,19 @@ class ContentManager:
|
||||
|
||||
# Nu we major_minor hebben, zoek de hoogste patch
|
||||
major_minor_path = os.path.join(content_path, major_minor)
|
||||
current_app.logger.debug(f"Major/Minor path: {major_minor_path}")
|
||||
|
||||
if not os.path.exists(major_minor_path):
|
||||
logger.error(f"Version path does not exist: {major_minor_path}")
|
||||
current_app.logger.error(f"Version path does not exist: {major_minor_path}")
|
||||
return None
|
||||
|
||||
files = os.listdir(major_minor_path)
|
||||
files = [f for f in os.listdir(major_minor_path) if not f.startswith('.')]
|
||||
current_app.logger.debug(f"Files in version path: {files}")
|
||||
version_files = []
|
||||
|
||||
for file in files:
|
||||
mm, p = self._parse_version(file)
|
||||
current_app.logger.debug(f"File: {file}, mm: {mm}, p: {p}")
|
||||
if mm == major_minor and p:
|
||||
version_files.append((mm, p, f"{mm}.{p}"))
|
||||
|
||||
@@ -99,10 +100,12 @@ class ContentManager:
|
||||
|
||||
# Sorteer op patch nummer
|
||||
version_files.sort(key=lambda v: int(v[1]))
|
||||
|
||||
current_app.logger.debug(f"Latest version: {version_files[-1]}")
|
||||
return version_files[-1]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error finding latest version for {content_type}: {str(e)}")
|
||||
current_app.logger.error(f"Error finding latest version for {content_type}: {str(e)}")
|
||||
return None
|
||||
|
||||
def read_content(self, content_type, major_minor=None, patch=None):
|
||||
@@ -125,11 +128,12 @@ class ContentManager:
|
||||
} of None bij fout
|
||||
"""
|
||||
try:
|
||||
current_app.logger.debug(f"Reading content {content_type}")
|
||||
# Als geen versie opgegeven, vind de laatste
|
||||
if not major_minor:
|
||||
version_info = self.get_latest_version(content_type)
|
||||
if not version_info:
|
||||
logger.error(f"No versions found for {content_type}")
|
||||
current_app.logger.error(f"No versions found for {content_type}")
|
||||
return None
|
||||
|
||||
major_minor, patch, full_version = version_info
|
||||
@@ -138,7 +142,7 @@ class ContentManager:
|
||||
elif not patch:
|
||||
version_info = self.get_latest_version(content_type, major_minor)
|
||||
if not version_info:
|
||||
logger.error(f"No versions found for {content_type} {major_minor}")
|
||||
current_app.logger.error(f"No versions found for {content_type} {major_minor}")
|
||||
return None
|
||||
|
||||
major_minor, patch, full_version = version_info
|
||||
@@ -147,14 +151,17 @@ class ContentManager:
|
||||
|
||||
# Nu hebben we major_minor en patch, lees het bestand
|
||||
file_path = self.get_content_path(content_type, major_minor, patch)
|
||||
current_app.logger.debug(f"Content File path: {file_path}")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
logger.error(f"Content file does not exist: {file_path}")
|
||||
current_app.logger.error(f"Content file does not exist: {file_path}")
|
||||
return None
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
|
||||
current_app.logger.debug(f"Content read: {content}")
|
||||
|
||||
return {
|
||||
'content': content,
|
||||
'version': full_version,
|
||||
@@ -162,7 +169,7 @@ class ContentManager:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading content {content_type} {major_minor}.{patch}: {str(e)}")
|
||||
current_app.logger.error(f"Error reading content {content_type} {major_minor}.{patch}: {str(e)}")
|
||||
return None
|
||||
|
||||
def list_content_types(self):
|
||||
@@ -171,7 +178,7 @@ class ContentManager:
|
||||
return [d for d in os.listdir(self.app.config['CONTENT_DIR'])
|
||||
if os.path.isdir(os.path.join(self.app.config['CONTENT_DIR'], d))]
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing content types: {str(e)}")
|
||||
current_app.logger.error(f"Error listing content types: {str(e)}")
|
||||
return []
|
||||
|
||||
def list_versions(self, content_type):
|
||||
@@ -211,5 +218,5 @@ class ContentManager:
|
||||
return versions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing versions for {content_type}: {str(e)}")
|
||||
current_app.logger.error(f"Error listing versions for {content_type}: {str(e)}")
|
||||
return []
|
||||
|
||||
@@ -43,6 +43,13 @@ configuration:
|
||||
allowed_values: ["No Information", "Active Interaction Only", "All Interactions"]
|
||||
default: "No Information"
|
||||
required: true
|
||||
form_title_display:
|
||||
name: "Form Title Display"
|
||||
description: Level of information shown for the Form Title
|
||||
type: "enum"
|
||||
allowed_values: ["No Title", "Full Title"]
|
||||
default: "Full Title"
|
||||
required: true
|
||||
active_background_color:
|
||||
name: "Active Interaction Background Color"
|
||||
description: "Primary Color"
|
||||
|
||||
@@ -1,37 +1,726 @@
|
||||
# Privacy Policy
|
||||
# Data Protection Agreement Ask Eve AI
|
||||
|
||||
## Version 1.0.0
|
||||
Ask Eve AI respects the privacy of their Customers, Partners, Users and End
|
||||
Users, and is strongly committed to keeping secure any information
|
||||
obtained from, for or about each of them. This Data Protection Agreement
|
||||
describes the practices with respect to Personal Data that Ask Eve AI
|
||||
collects from or about Customers, Partners, Users and End Users when
|
||||
they use the applications and services of Ask Eve AI (collectively,
|
||||
"Services").
|
||||
|
||||
*Effective Date: 2025-06-03*
|
||||
## Definitions
|
||||
|
||||
### 1. Introduction
|
||||
**Data Controller and Data Processor**: have each the meanings set out in
|
||||
the Data Protection Legislation;
|
||||
|
||||
This Privacy Policy describes how EveAI collects, uses, and discloses your information when you use our services.
|
||||
*Data Protection Legislation:* means the European Union's General Data
|
||||
Protection Regulation 2016/679 on the protection of natural persons with
|
||||
regard to the processing of personal data and on the free movement of
|
||||
such data ("GDPR") and all applicable laws and regulations relating to
|
||||
the processing of personal data and privacy and any amendment or
|
||||
re-enactment of any of them;
|
||||
|
||||
### 2. Information We Collect
|
||||
*Data Subject:* has the meaning set out in the Data Protection
|
||||
Legislation and shall refer, in this Data Processing Agreement to the
|
||||
identified or identifiable individual(s) whose Personal Data is/are
|
||||
under control of the Data Controller and is/are the subject of the
|
||||
Processing by the Data Processor in the context of the Services;
|
||||
|
||||
We collect information you provide directly to us, such as account information, content you process through our services, and communication data.
|
||||
*Personal Data*: has the meaning set out in the Data Protection
|
||||
Legislation and shall refer, in this Data Processing Agreement to any
|
||||
information relating to the Data Subject that is subject to the
|
||||
Processing in the context of the Services;
|
||||
|
||||
### 3. How We Use Your Information
|
||||
*Processing*: has the meaning given to that term in the Data Protection
|
||||
Legislation and "process" and "processed" shall have a corresponding
|
||||
meaning;
|
||||
|
||||
We use your information to provide, maintain, and improve our services, process transactions, send communications, and comply with legal obligations.
|
||||
*Purposes*: shall mean the limited, specific and legitimate purposes of
|
||||
the Processing as described in the Agreement;
|
||||
|
||||
### 4. Data Security
|
||||
*Regulators:* means those government departments and regulatory,
|
||||
statutory and other bodies, entities and committees which, whether under
|
||||
statute, rule, regulation, code of practice or otherwise, are entitled
|
||||
to regulate, investigate or influence the privacy matters dealt with in
|
||||
agreements and/or by the parties to the agreements (as the case may be);
|
||||
|
||||
We implement appropriate security measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.
|
||||
*Sub-Processor:* shall mean the subcontractor(s) listed in Annex 1,
|
||||
engaged by the Data Processor to Process Personal Data on behalf of the
|
||||
Data Controller and in accordance with its instructions, the terms of
|
||||
this Data Processing Agreement and the terms of the written subcontract
|
||||
to be entered into with the Sub-Processor;
|
||||
|
||||
### 5. International Data Transfers
|
||||
*Third Country:* means a country outside the European Economic Area that
|
||||
is not considered by the European Commission as offering an adequate
|
||||
level of protection in accordance with Article 44 of the European
|
||||
Union's General Data Protection Regulation 679/2016.
|
||||
|
||||
Your information may be transferred to and processed in countries other than the country you reside in, where data protection laws may differ.
|
||||
*Tenant / Customer*: A tenant is the organisation, enterprise or company
|
||||
subscribing to the services of Ask Eve AI. Same as Customer, but more in
|
||||
context of a SAAS product like Ask Eve AI.
|
||||
|
||||
### 6. Your Rights
|
||||
*Partner*: Any organisation, enterprise or company that offers services
|
||||
or knowledge on top of the Ask Eve AI platform.
|
||||
|
||||
Depending on your location, you may have certain rights regarding your personal information, such as access, correction, deletion, or restriction of processing.
|
||||
*Account / User*: A user is a natural person performing activities like
|
||||
configuration or testing in Ask Eve AI, working within the context of a
|
||||
Tenant. A user is explicitly registered within the system as a member of
|
||||
the tenant.
|
||||
|
||||
### 7. Changes to This Policy
|
||||
*End User*: An end user is every person making use of Ask Eve AI's services,
|
||||
in the context of Ask Eve AI services exposed by the tenant
|
||||
(e.g. a chatbot). This user is not explicitly registered within the
|
||||
system.
|
||||
|
||||
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
|
||||
*Ask Eve AI Platform*: The Ask Eve AI Platform (also referred to as
|
||||
"Evie" or "platform") is the combination of software components and
|
||||
products, code, configuration and prompts that allow Ask Eve AI to
|
||||
perform its activities.
|
||||
|
||||
### 8. Contact Us
|
||||
*Ask Eve AI Services*: Is the collection of all services on top of the
|
||||
Ask Eve AI Platform offered to all users of the platform (Tenants,
|
||||
Partners, Users and End Users), including all services exposed by
|
||||
Partners on the Ask Eve AI platform.
|
||||
|
||||
If you have any questions about this Privacy Policy, please contact us at privacy@askeveai.be.
|
||||
*Partner Services:* Is the collection of all services and applications built on top of
|
||||
the Ask Eve AI Platform offered by Partners. This excludes services
|
||||
connected through API's to the Ask Eve AI platform or services connected
|
||||
to the platform by any other means.
|
||||
|
||||
## Qualification of Parties
|
||||
|
||||
2.1 As part of the provision of the Services, Partner and Customer may
|
||||
engage Ask Eve AI to collect, process and/or use Personal Data on its
|
||||
behalf and/or Ask Eve AI may be able to access Personal Data and
|
||||
accordingly, in relation to the Agreement, the Parties agree that Partner
|
||||
or Customer is the Data Controller and Ask Eve AI is the Data Processor.
|
||||
|
||||
2.2 From time to time, Partner or Customer may request Ask Eve AI to
|
||||
collect, process and/or use Personal Data on behalf of a third party for
|
||||
which Ask Eve AI may be able to access Personal Data and accordingly, in
|
||||
relation to the Agreement, the Parties agree that Customer is the Data
|
||||
Processor and Ask Eve AI is the Data Sub-Processor.
|
||||
|
||||
# Data Classification
|
||||
|
||||
Ask Eve AI classifies data as follows:
|
||||
|
||||
# Data Protection {#data-protection-1}
|
||||
|
||||
The Data Processor warrants, represents and undertakes to the Data
|
||||
Controller that it shall only process the Personal Data as limited in de
|
||||
following paragraphs.
|
||||
|
||||
**System Data:**
|
||||
|
||||
Ask Eve AI System Data is the data required to enable Ask Eve AI to:
|
||||
|
||||
- authenticate and authorise accounts / users
|
||||
- authenticate and authorise automated interfaces (APIs, sockets,
|
||||
integrations)
|
||||
- to invoice according to subscription and effective usage of Ask Eve
|
||||
AI's services
|
||||
|
||||
The following personal information is gathered:
|
||||
|
||||
1. *Account / User Information*: This information enables a user to log
|
||||
into the Ask Eve AI systems, or to subscribe to the system's
|
||||
services. It includes name, e-mail address, a secured password and
|
||||
roles in the system.
|
||||
2. *Tenant / Customer Information*: Although not personal data in the
|
||||
strict sense, in order to subscribe to the services provided by Ask
|
||||
Eve AI, payment information such as financial details, VAT numbers,
|
||||
valid addresses and email information is required.
|
||||
|
||||
**Tenant Data:**
|
||||
|
||||
Tenant data is all information that is added to Ask Eve AI by
|
||||
|
||||
- one of the tenant's registered accounts
|
||||
- one of the automated interfaces (APIs, sockets, integrations)
|
||||
authorised by the tenant
|
||||
- interaction by one of the end users that has access to Ask Eve AI's
|
||||
services exposed by the tenant
|
||||
|
||||
This data is required to enable Ask Eve AI to perform the
|
||||
tenant-specific functions requested or defined by the Tenant, such as
|
||||
enabling AI chatbots or AI specialists to work on tenant specific
|
||||
information.
|
||||
|
||||
There's no personal data collected explicitly, however, the following
|
||||
personal information is gathered:
|
||||
|
||||
1. *End User Content*: Ask Eve AI collects Personal Data that the End
|
||||
User provides in the input to our Services ("Content") as is.
|
||||
2. *Communication Information*: If the Customer communicates with Ask
|
||||
Eve AI, such as via email, our pages on social media sites or the
|
||||
chatbots or other interfaces we provide to our services, Ask Eve AI
|
||||
may collect Personal Data like name, contact information, and the
|
||||
contents of the messages the Customer sends ("Communication
|
||||
Information"). End User personal information may be provided by End
|
||||
User in interactions with Ask Eve AI's services, and as such will be
|
||||
stored in Ask Eve AI's services as is.
|
||||
|
||||
>
|
||||
|
||||
> **User Data:**
|
||||
|
||||
> Ask Eve AI collects information the User may provide to Ask Eve AI,
|
||||
> such as when you participate in our events, surveys, ask us to get in
|
||||
> contact or provide us with information to establish your identity or
|
||||
> age.
|
||||
|
||||
>
|
||||
|
||||
>
|
||||
|
||||
> \
|
||||
|
||||
**Technical Data:**\
|
||||
When you visit, use, or interact with the Services, we receive the
|
||||
following information about your visit, use, or interactions ("Technical
|
||||
Information"):
|
||||
|
||||
1. *Log Data:* Ask Eve AI collects information that your browser or
|
||||
device automatically sends when the Customer uses the Services. Log
|
||||
data includes the Internet Protocol address, browser type and
|
||||
settings, the date and time of your request, and how the Customer
|
||||
interacts with the Services.
|
||||
2. *Usage Data:* Ask Eve AI collects information about the use of the
|
||||
Services, such as the types of content that the Customer views or
|
||||
engages with, the features the Customer uses and the actions the
|
||||
Customer takes, as well as the Customer's time zone, country, the
|
||||
dates and times of access, user agent and version, type of computer
|
||||
or mobile device, and the Customer's computer connection.
|
||||
3. *Interaction Data*: Ask Eve AI collects the data you provide when
|
||||
interacting with it's services, such as interacting with a chatbot
|
||||
or similar advanced means.
|
||||
4. *Device Information:* Ask Eve AI collects information about the
|
||||
device the Customer uses to access the Services, such as the name of
|
||||
the device, operating system, device identifiers, and browser you
|
||||
are using. Information collected may depend on the type of device
|
||||
the Customer uses and its settings.
|
||||
5. *Location Information:* Ask Eve AI may determine the general area
|
||||
from which your device accesses our Services based on information
|
||||
like its IP address for security reasons and to make your product
|
||||
experience better, for example to protect the Customer's account by
|
||||
detecting unusual login activity or to provide more accurate
|
||||
responses. In addition, some of our Services allow the Customer to
|
||||
choose to provide more precise location information from the
|
||||
Customer's device, such as location information from your device's
|
||||
GPS.
|
||||
6. *Cookies and Similar Technologies:* Ask Eve AI uses cookies and
|
||||
similar technologies to operate and administer our Services, and
|
||||
improve your experience. If the Customer uses the Services without
|
||||
creating an account, Ask Eve AI may store some of the information
|
||||
described in this Agreement with cookies, for example to help
|
||||
maintain the Customer's preferences across browsing sessions. For
|
||||
details about our use of cookies, please read our Cookie Policy.
|
||||
|
||||
**External Data:**
|
||||
|
||||
Information Ask Eve AI receives from other sources:
|
||||
|
||||
Ask Eve AI receives information from trusted partners, such as security
|
||||
partners, to protect against fraud, abuse, and other security threats to
|
||||
the Services, and from marketing vendors who provide us with information
|
||||
about potential customers of our business services.
|
||||
|
||||
Ask Eve AI also collects information from other sources, like
|
||||
information that is publicly available on the internet, to develop the
|
||||
models that power the Services.
|
||||
|
||||
Ask Eve AI may use Personal Data for the following purposes:
|
||||
|
||||
- To provide, analyse, and maintain the Services, for example to respond
|
||||
to the Customer's questions for Ask Eve AI;
|
||||
- To improve and develop the Services and conduct research, for example
|
||||
to develop new product features;
|
||||
- To communicate with the Customer, including to send the Customer
|
||||
information about our Services and events, for example about changes
|
||||
or improvements to the Services;
|
||||
- To prevent fraud, illegal activity, or misuses of our Services, and to
|
||||
protect the security of our systems and Services;
|
||||
- To comply with legal obligations and to protect the rights, privacy,
|
||||
safety, or property of our users or third parties.
|
||||
|
||||
Ask Eve AI may also aggregate or de-identify Personal Data so that it no
|
||||
longer identifies the Customer and use this information for the purposes
|
||||
described above, such as to analyse the way our Services are being used,
|
||||
to improve and add features to them, and to conduct research. Ask Eve AI
|
||||
will maintain and use de-identified information in de-identified form
|
||||
and not attempt to reidentify the information, unless required by law.
|
||||
|
||||
As noted above, Ask Eve AI may use content the Customer provides Ask Eve
|
||||
AI to improve the Services, for example to train the models that power
|
||||
Ask Eve AI. Read [**our instructions**(opens in a new
|
||||
window)**](https://help.openai.com/en/articles/5722486-how-your-data-is-used-to-improve-model-performance) on
|
||||
how you can opt out of our use of your Content to train our models.\
|
||||
|
||||
1. 1. ## Instructions {#instructions-3}
|
||||
|
||||
Data Processor shall only Process Personal Data of Data Controller on
|
||||
behalf of the Data Controller and in accordance with this Data
|
||||
Processing Agreement, solely for the Purposes and the eventual
|
||||
instructions of the Data Controller, and to the extent, and in such a
|
||||
manner, as is reasonably necessary to provide the Services in accordance
|
||||
with the Agreement. Data Controller shall only give instructions that
|
||||
comply with the Data Protection legislation.
|
||||
|
||||
2. 1. ## Applicable mandatory laws {#applicable-mandatory-laws-3}
|
||||
|
||||
Data Processor shall only Process as required by applicable mandatory
|
||||
laws and always in compliance with Data Protection Legislation.\
|
||||
|
||||
3. 1. ## Transfer to a third party {#transfer-to-a-third-party-3}
|
||||
|
||||
Data Processor uses functionality of third party services to realise
|
||||
it's functionality. For the purpose of realising Ask Eve AI's
|
||||
functionality, and only for this purpose, information is sent to it's
|
||||
sub-processors.
|
||||
|
||||
Data Processor shall not transfer or disclose any Personal Data to any
|
||||
other third party and/or appoint any third party as a sub-processor of
|
||||
Personal Data unless it is legally required or in case of a notification
|
||||
to the Data Controller by which he gives his consent.
|
||||
|
||||
4. 1. ## Transfer to a Third Country {#transfer-to-a-third-country-3}
|
||||
|
||||
Data Processor shall not transfer Personal Data (including any transfer
|
||||
via electronic media) to any Third Country without the prior written
|
||||
consent of the Data Controller by exception of the following.
|
||||
|
||||
The Parties agree that Personal Data can only be transferred to and/or
|
||||
kept with the recipient outside the European Economic Area (EEA) in a
|
||||
country that not falls under an adequacy decision issued by the European
|
||||
Commission by exception and only if necessary to comply with the
|
||||
obligations of this Agreement or when legally required. Such transfer
|
||||
shall be governed by the terms of a data transfer agreement containing
|
||||
standard contractual clauses as published in the Decision of the
|
||||
European Commission of June 4, 2021 (Decision (EU) 2021/914), or by
|
||||
other mechanisms foreseen by the applicable data protection law.
|
||||
|
||||
The Data Processor shall prior to the international transfer inform the
|
||||
Data Controller about the particular measures taken to guarantee the
|
||||
protection of the Personal Data of the Data Subject in accordance with
|
||||
the Regulation.
|
||||
|
||||
\
|
||||
|
||||
5. 1. ## Data secrecy {#data-secrecy-3}
|
||||
|
||||
The Data Processor shall maintain data secrecy in accordance with
|
||||
applicable Data Protection Legislation and shall take all reasonable
|
||||
steps to ensure that:
|
||||
|
||||
> \(1\) only those Data Processor personnel and the Sub-Processor
|
||||
> personnel that need to have access to Personal Data are given access
|
||||
> and only to the extent necessary to provide the Services; and
|
||||
|
||||
> \(2\) the Data Processor and the Sub-Processor personnel entrusted
|
||||
> with the processing of, or who may have access to, Personal Data are
|
||||
> reliable, familiar with the requirements of data protection and
|
||||
> subject to appropriate obligations of confidentiality and data secrecy
|
||||
> in accordance with applicable Data Protection Legislation and at all
|
||||
> times act in compliance with the Data Protection Obligations.
|
||||
|
||||
6. 1. ## Appropriate technical and organizational measures {#appropriate-technical-and-organizational-measures-3}
|
||||
|
||||
Data Processor has implemented (and shall comply with) all appropriate
|
||||
technical and organizational measures to ensure the security of the
|
||||
Personal Data, to ensure that processing of the Personal Data is
|
||||
performed in compliance with the applicable Data Protection Legislation
|
||||
and to ensure the protection of the Personal Data against accidental or
|
||||
unauthorized access, alteration, destruction, damage, corruption or loss
|
||||
as well as against any other unauthorized or unlawful processing or
|
||||
disclosure ("Data Breach"). Such measures shall ensure best practice
|
||||
security, be compliant with Data Protection Legislation at all times and
|
||||
comply with the Data Controller's applicable IT security policies.
|
||||
|
||||
Data Controller has also introduced technical and organizational
|
||||
measures, and will continue to introduce them to protect its Personal
|
||||
Data from accidental or unlawful destruction or accidental loss,
|
||||
alteration, unauthorized disclosure or access. For the sake of clarity,
|
||||
the Data Controller is responsible for the access control policy,
|
||||
registration, de-registration and withdrawal of the access rights of the
|
||||
Users or Consultant(s) to its systems, for the access control,
|
||||
registration, de-registration and withdrawal of automation access codes
|
||||
(API Keys), and is also responsible for the complete physical security
|
||||
of its environment.
|
||||
|
||||
7. 1. ## Assistance and co-operation {#assistance-and-co-operation-3}
|
||||
|
||||
The Data Processor shall provide the Data Controller with such
|
||||
assistance and co-operation as the Data Controller may reasonably
|
||||
request to enable the Data Controller to comply with any obligations
|
||||
imposed on it by Data Protection Legislation in relation to Personal
|
||||
Data processed by the Data Processor, including but not limited to:
|
||||
|
||||
> \(1\) on request of the Data Controller, promptly providing written
|
||||
> information regarding the technical and organizational measures which
|
||||
> the Data Processor has implemented to safeguard Personal Data;\
|
||||
|
||||
> \(2\) disclosing full and relevant details in respect of any and all
|
||||
> government, law enforcement or other access protocols or controls
|
||||
> which it has implemented, but only in so far this information is
|
||||
> available to the Data Processor;
|
||||
|
||||
> \(3\) notifying the Data Controller as soon as possible and as far as
|
||||
> it is legally permitted to do so, of any access request for disclosure
|
||||
> of data which concerns Personal Data (or any part thereof) by any
|
||||
> Regulator, or by a court or other authority of competent jurisdiction.
|
||||
> For the avoidance of doubt and as far as it is legally permitted to do
|
||||
> so, the Data Processor shall not disclose or release any Personal Data
|
||||
> in response to such request served on the Data Processor without first
|
||||
> consulting with and obtaining the written consent of the Data
|
||||
> Controller; and
|
||||
|
||||
> \(4\) notifying the Data Controller as soon as possible of any legal
|
||||
> or factual circumstances preventing the Data Processor from executing
|
||||
> any of the instructions of the Data Controller.
|
||||
|
||||
> \(5\) notifying the Data Controller as soon as possible of any request
|
||||
> received directly from a Data Subject regarding the Processing of
|
||||
> Personal Data, without responding to such request. For the avoidance
|
||||
> of doubt, the Data Controller is solely responsible for handling and
|
||||
> responding to such requests.
|
||||
|
||||
> \(6\) notifying the Data Controller immediately in writing if it
|
||||
> becomes aware of any Data Breach and provide the Data Controller, as
|
||||
> soon as possible, with information relating to a Data Breach,
|
||||
> including, without limitation, but only insofar this information is
|
||||
> readily available to the Data Processor: the nature of the Data Breach
|
||||
> and the Personal Data affected, the categories and number of Data
|
||||
> Subjects concerned, the number of Personal Data records concerned,
|
||||
> measures taken to address the Data Breach, the possible consequences
|
||||
> and adverse effect of the Data Breach .
|
||||
|
||||
> \(7\) Where the Data Controller is legally required to provide
|
||||
> information regarding the Personal Data Processed by Data Processor
|
||||
> and its Processing to any Data Subject or third party, the Data
|
||||
> Processor shall support the Data Controller in the provision of such
|
||||
> information when explicitly requested by the Data Controller.
|
||||
|
||||
4. # Audit {#audit-1}
|
||||
|
||||
At the Data Controller's request the Data Processor shall provide the
|
||||
Data Controller with all information needed to demonstrate that it
|
||||
complies with this Data Processing Agreement The Data Processor shall
|
||||
permit the Data Controller, or a third-party auditor acting under the
|
||||
Data Controller's direction, (but only to the extent this third-party
|
||||
auditor cannot be considered a competitor of the Data Processor), to
|
||||
conduct, at the Data Controller's cost (for internal and external
|
||||
costs), a data privacy and security audit, concerning the Data
|
||||
Processor's data security and privacy procedures relating to the
|
||||
processing of Personal Data, and its compliance with the Data Protection
|
||||
Obligations, but not more than once per contract year. The Data
|
||||
Controller shall provide the Data Processor with at least thirty (30)
|
||||
days prior written notice of its intention to perform an audit. The
|
||||
notification must include the name of the auditor, a description of the
|
||||
purpose and the scope of the audit. The audit has to be carried out in
|
||||
such a way that the inconvenience for the Data Processor is kept to a
|
||||
minimum, and the Data Controller shall impose sufficient confidentiality
|
||||
obligations on its auditors. Every auditor who does an inspection will
|
||||
be at all times accompanied by a dedicated employee of the Processor.
|
||||
|
||||
4. # Liability {#liability-1}
|
||||
|
||||
Each Party shall be liable for any suffered foreseeable, direct and
|
||||
personal damages ("Direct Damages") resulting from any attributable
|
||||
breach of its obligations under this Data Processing Agreement. If one
|
||||
Party is held liable for a violation of its obligations hereunder, it
|
||||
undertakes to indemnify the non-defaulting Party for any Direct Damages
|
||||
resulting from any attributable breach of the defaulting Party's
|
||||
obligations under this Data Processing Agreement or any fault or
|
||||
negligence to the performance of this Data Processing Agreement. Under
|
||||
no circumstances shall the Data Processor be liable for indirect,
|
||||
incidental or consequential damages, including but not limited to
|
||||
financial and commercial losses, loss of profit, increase of general
|
||||
expenses, lost savings, diminished goodwill, damages resulting from
|
||||
business interruption or interruption of operation, damages resulting
|
||||
from claims of customers of the Data Controller, disruptions of
|
||||
planning, loss of anticipated profit, loss of capital, loss of
|
||||
customers, missed opportunities, loss of advantages or corruption and/or
|
||||
loss of files resulting from the performance of the Agreement.
|
||||
|
||||
[]{#anchor}[]{#anchor-1}[]{#anchor-2}[]{#anchor-3}If it appears that
|
||||
both the Data Controller and the Data Processor are responsible for the
|
||||
damage caused by the processing of Personal Data, both Parties shall be
|
||||
liable and pay damages, in accordance with their individual share in the
|
||||
responsibility for the damage caused by the processing.
|
||||
|
||||
[]{#anchor-4}[]{#anchor-5}[]{#anchor-6}In any event the total liability
|
||||
of the Data Processor under this Agreement shall be limited to the cause
|
||||
of damage and to the amount that equals the total amount of fees paid by
|
||||
the Data Controller to the Data Processor for the delivery and
|
||||
performance of the Services for a period not more than twelve months
|
||||
immediately prior to the cause of damages. In no event shall the Data
|
||||
Processor be held liable if the Data Processor can prove he is not
|
||||
responsible for the event or cause giving rise to the damage.
|
||||
|
||||
4. # Term {#term-1}
|
||||
|
||||
This Data Processing Agreement shall be valid for as long as the
|
||||
Customer uses the Services.
|
||||
|
||||
After the termination of the Processing of the Personal Data or earlier
|
||||
upon request of the Data Controller, the Data Processor shall cease all
|
||||
use of Personal Data and delete all Personal Data and copies thereof in
|
||||
its possession unless otherwise agreed or when deletion of the Personal
|
||||
Data should be technically impossible.
|
||||
|
||||
4. # Governing law -- jurisdiction {#governing-law-jurisdiction-1}
|
||||
|
||||
This Data Processing Agreement and any non-contractual obligations
|
||||
arising out of or in connection with it shall be governed by and
|
||||
construed in accordance with Belgian Law.
|
||||
|
||||
Any litigation relating to the conclusion, validity, interpretation
|
||||
and/or performance of this Data Processing Agreement or of subsequent
|
||||
contracts or operations derived therefrom, as well as any other
|
||||
litigation concerning or related to this Data Processing Agreement,
|
||||
without any exception, shall be submitted to the exclusive jurisdiction
|
||||
of the courts of Gent, Belgium.
|
||||
|
||||
# Annex1
|
||||
|
||||
# Sub-Processors
|
||||
|
||||
The Data Controller hereby agrees to the following list of
|
||||
Sub-Processors, engaged by the Data Processor for the Processing of
|
||||
Personal Data under the Agreement:
|
||||
|
||||
+-------------+--------------------------------------------------------+
|
||||
| | |
|
||||
+=============+========================================================+
|
||||
| **Open AI** | |
|
||||
+-------------+--------------------------------------------------------+
|
||||
| Address | OpenAI, L.L.C., |
|
||||
| | |
|
||||
| | 3180 18th St, San Francisco, |
|
||||
| | |
|
||||
| | CA 94110, |
|
||||
| | |
|
||||
| | United States of America. |
|
||||
+-------------+--------------------------------------------------------+
|
||||
| Contact | OpenAI's Data Protection team |
|
||||
| | |
|
||||
| | dsar@openai.com |
|
||||
+-------------+--------------------------------------------------------+
|
||||
| Description | Ask Eve AI accesses Open AI's models through Open AI's |
|
||||
| | API to realise it's functionality. |
|
||||
| | |
|
||||
| | Services are GDPR compliant. |
|
||||
+-------------+--------------------------------------------------------+
|
||||
| | |
|
||||
+-------------+--------------------------------------------------------+
|
||||
|
||||
+---------------+------------------------------------------------------+
|
||||
| | |
|
||||
+===============+======================================================+
|
||||
| **StackHero** | |
|
||||
+---------------+------------------------------------------------------+
|
||||
| Address | Stackhero |
|
||||
| | |
|
||||
| | 1 rue de Stockholm |
|
||||
| | |
|
||||
| | 75008 Paris |
|
||||
| | |
|
||||
| | France |
|
||||
+---------------+------------------------------------------------------+
|
||||
| Contact | support@stackhero.io |
|
||||
+---------------+------------------------------------------------------+
|
||||
| Description | StackHero is Ask Eve AI's cloud provider, and hosts |
|
||||
| | the services for PostgreSQL, Redis, Docker, Minio |
|
||||
| | and Greylog. |
|
||||
| | |
|
||||
| | Services are GDPR compliant. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| **** | |
|
||||
+---------------+------------------------------------------------------+
|
||||
|
||||
+----------------+-----------------------------------------------------+
|
||||
| | |
|
||||
+================+=====================================================+
|
||||
| **A2 Hosting** | |
|
||||
+----------------+-----------------------------------------------------+
|
||||
| Address | A2 Hosting, Inc. |
|
||||
| | |
|
||||
| | PO Box 2998 |
|
||||
| | |
|
||||
| | Ann Arbor, MI 48106 |
|
||||
| | |
|
||||
| | United States |
|
||||
+----------------+-----------------------------------------------------+
|
||||
| Contact | [*+1 734-222-4678*](tel:+1(734)222-4678) |
|
||||
+----------------+-----------------------------------------------------+
|
||||
| Description | A2 hosting is hosting our main webserver and |
|
||||
| | mailserver. They are all hosted on European servers |
|
||||
| | (Iceland). It does not handle data of our business |
|
||||
| | applications. |
|
||||
| | |
|
||||
| | Services are GDPR compliant. |
|
||||
+----------------+-----------------------------------------------------+
|
||||
| **** | |
|
||||
+----------------+-----------------------------------------------------+
|
||||
|
||||
# Annex 2
|
||||
|
||||
# []{#anchor-7}Technical and organizational measures
|
||||
|
||||
# 1. Purpose of this document
|
||||
|
||||
This document contains an overview of the technical and operational
|
||||
measures which are applicable by default within Ask Eve AI. The actual
|
||||
measures taken depend on the services provided and the specific customer
|
||||
context. Ask Eve AI guarantees it has for all its services and sites the
|
||||
necessary adequate technical and operational measures included in the
|
||||
list below following a Data Protection Impact Assessment (DPIA).
|
||||
|
||||
These measures are designed to:
|
||||
|
||||
1. ensure the security and confidentiality of Ask Eve AI managed data,
|
||||
information, applications and infrastructure;
|
||||
2. protect against any anticipated threats or hazards to the security
|
||||
and integrity of Personal Data, Ask Eve AI Intellectual Property,
|
||||
Infrastructure or other business-critical assets;
|
||||
3. protect against any actual unauthorized processing, loss, use,
|
||||
disclosure or acquisition of or access to any Personal Data or other
|
||||
business-critical information or data managed by Ask Eve AI.
|
||||
|
||||
Ask Eve AI ensures that all its Sub-Processors have provided the
|
||||
necessary and required guarantees on the protection of personal data
|
||||
they process on Ask Eve AI's behalf.
|
||||
|
||||
Ask Eve AI continuously monitors the effectiveness of its information
|
||||
safeguards and organizes a yearly compliance audit by a Third Party to
|
||||
provide assurance on the measures and controls in place.
|
||||
|
||||
# 2. Technical & Organizational Measures
|
||||
|
||||
Ask Eve AI has designed, invested and implemented a dynamic
|
||||
multi-layered security architecture protecting its endpoints, locations,
|
||||
cloud services and custom-developed business applications against
|
||||
today's variety of cyberattacks ranging from spear phishing, malware,
|
||||
viruses to intrusion, ransomware and data loss / data breach incidents
|
||||
by external and internal bad actors.
|
||||
|
||||
This architecture, internationally recognized and awarded, is a
|
||||
combination of automated proactive, reactive and forensic quarantine
|
||||
measures and Ask Eve AI internal awareness and training initiatives that
|
||||
creates and end-to-end chain of protection to identify, classify and
|
||||
stop any potential malicious action on Ask Eve AI's digital
|
||||
infrastructure. Ask Eve AI uses an intent-based approach where
|
||||
activities are constantly monitored, analysed and benchmarked instead of
|
||||
relying solely on a simple authentication/authorization trust model.
|
||||
|
||||
4. 1. ## General Governance & Awareness {#general-governance-awareness-3}
|
||||
|
||||
As a product company, Ask Eve AI is committed to maintain and preserve
|
||||
an IT infrastructure that has a robust security architecture, complies
|
||||
with data regulation policies and provides a platform to its employees
|
||||
for flexible and effective work and collaboration activities with each
|
||||
other and our customers.
|
||||
|
||||
Ask Eve AI IT has a cloud-first and cloud-native strategy and as such
|
||||
works with several third-party vendors that store and process our
|
||||
company data. Ask Eve AI IT aims to work exclusively with vendors that
|
||||
are compliant with the national and European Data Protection
|
||||
Regulations. Transfers of Personal Data to third-countries are subject
|
||||
to compliance by the third-country Processor/Sub-Processor with the
|
||||
Standard Contractual Clauses as launched by virtue of the EU Commission
|
||||
Decision 2010/87/EU of 5 February 2010 as updated by the EU Comission
|
||||
Decision (EU) 2021/914 of 4 June 2021, unless the third country of the
|
||||
Processor/Sub-Processor has been qualified as providing an adequate
|
||||
level of protection for Personal Data by the European Commission, (a.o.
|
||||
EU-U.S. Data Privacy Framework).
|
||||
|
||||
Ask Eve AI has an extensive IT policy applicable to any employee or
|
||||
service provider that uses Ask Eve AI platforms or infrastructure. This
|
||||
policy informs the user of his or her rights & duties and informs the
|
||||
user of existing monitoring mechanisms to enforce security and data
|
||||
compliance. The policy is updated regularly and an integrated part of
|
||||
new employee onboarding and continuous training and development
|
||||
initiatives on internal tooling and cyber security;
|
||||
|
||||
Ask Eve AI IT has several internal policies on minimal requirements
|
||||
before an application, platform or tool can enter our application
|
||||
landscape. These include encryption requirements, DLP requirements,
|
||||
transparent governance & licensing requirements and certified support
|
||||
contract procedures & certifications;
|
||||
|
||||
These policies are actively enforced through our endpoint security, CASB
|
||||
and cloud firewall solutions. Any infraction on these policies is met
|
||||
with appropriate action and countermeasures and may result in a complete
|
||||
ban from using and accessing Ask Eve AI's infrastructure and platforms
|
||||
or even additional legal action against employees, clients or other
|
||||
actors;
|
||||
|
||||
## 9.2. Physical Security & Infrastructure
|
||||
|
||||
Ask Eve AI has deployed industry-standard physical access controls to
|
||||
its location for employee presence and visitor management.
|
||||
|
||||
Restricted environments including network infrastructure, data center
|
||||
and server rooms are safeguarded by additional access controls and
|
||||
access to these rooms is audited. CCTV surveillance is present in all
|
||||
restricted and critical areas.
|
||||
|
||||
Fire alarm and firefighting systems are implemented for employee and
|
||||
visitor safety. Regular fire simulations and evacuation drills are
|
||||
performed.
|
||||
|
||||
Clean desk policies are enforced, employees regularly in contact with
|
||||
sensitive information have private offices and follow-me printing
|
||||
enabled.
|
||||
|
||||
Key management governance is implemented and handled by Facilities.
|
||||
|
||||
1. 1. ## Endpoint Security & User Accounts {#endpoint-security-user-accounts-3}
|
||||
|
||||
All endpoints and any information stored are encrypted using
|
||||
enterprise-grade encryption on all operating systems supported by Ask
|
||||
Eve AI.
|
||||
|
||||
Ask Eve AI has implemented a centrally managed anti-virus and malware
|
||||
protection system for endpoints, email and document stores.
|
||||
|
||||
Multifactor Authentication is enforced on all user accounts where
|
||||
possible.
|
||||
|
||||
Conditional Access is implemented across the entire infrastructure
|
||||
limiting access to specific regions and setting minimum requirements for
|
||||
the OS version, network security level, endpoint protection level and
|
||||
user behavior.
|
||||
|
||||
Only vendor supplied updates are installed.
|
||||
|
||||
Ask Eve AI has deployed a comprehensive device management strategy to
|
||||
ensure endpoint integrity and policy compliance.
|
||||
|
||||
Access is managed according to role-based access control principles and
|
||||
all user behavior on Ask Eve AI platforms is audited.
|
||||
|
||||
1. 1. ## Data Storage, Recovery & Securing Personal Data {#data-storage-recovery-securing-personal-data-3}
|
||||
|
||||
> Ask Eve AI has deployed:
|
||||
|
||||
- An automated multi-site encrypted back-up process with daily integrity
|
||||
reviews.
|
||||
- The possibility for the anonymization, pseudonymization and encryption
|
||||
of Personal Data.
|
||||
- The ability to monitor and ensure the ongoing confidentiality,
|
||||
integrity, availability and resilience of processing systems and
|
||||
services.
|
||||
- The ability to restore the availability and access to Personal Data in
|
||||
a timely manner in the event of a physical or technical incident.
|
||||
- A logical separation between its own data, the data of its customers
|
||||
and suppliers.
|
||||
- A process to keep processed data accurate, reliable and up-to-date.
|
||||
- Records of the processing activities.
|
||||
- Data Retention Policies
|
||||
|
||||
1. 1. ## Protection & Insurance {#protection-insurance-3}
|
||||
|
||||
Ask Eve AI has a cyber-crime insurance policy. Details on the policy can
|
||||
be requested through the legal department.
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// eveai_chat_client/static/assets/js/composables/useContentModal.js
|
||||
|
||||
import { ref, reactive, provide, inject } from 'vue';
|
||||
|
||||
// Injection key for the composable
|
||||
export const CONTENT_MODAL_KEY = Symbol('contentModal');
|
||||
|
||||
// Create the composable
|
||||
export function useContentModal() {
|
||||
// Reactive state for modal
|
||||
const modalState = reactive({
|
||||
show: false,
|
||||
title: '',
|
||||
content: '',
|
||||
version: '',
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
lastContentUrl: '' // Add this for retry functionality
|
||||
});
|
||||
|
||||
// Content cache to avoid repeated API calls
|
||||
const contentCache = ref({});
|
||||
|
||||
// Show modal with content
|
||||
const showModal = async (options = {}) => {
|
||||
const {
|
||||
title = 'Content',
|
||||
contentUrl = null,
|
||||
content = null,
|
||||
version = ''
|
||||
} = options;
|
||||
|
||||
// Set initial state
|
||||
modalState.show = true;
|
||||
modalState.title = title;
|
||||
modalState.content = content || '';
|
||||
modalState.version = version;
|
||||
modalState.loading = !!contentUrl; // Only show loading if we need to fetch content
|
||||
modalState.error = false;
|
||||
modalState.errorMessage = '';
|
||||
modalState.lastContentUrl = contentUrl || ''; // Store for retry functionality
|
||||
|
||||
// If content is provided directly, no need to fetch
|
||||
if (content) {
|
||||
modalState.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If contentUrl is provided, fetch the content
|
||||
if (contentUrl) {
|
||||
await loadContent(contentUrl);
|
||||
}
|
||||
};
|
||||
|
||||
// Hide modal
|
||||
const hideModal = () => {
|
||||
console.log('Hiding content modal');
|
||||
modalState.show = false;
|
||||
modalState.title = '';
|
||||
modalState.content = '';
|
||||
modalState.version = '';
|
||||
modalState.loading = false;
|
||||
modalState.error = false;
|
||||
modalState.errorMessage = '';
|
||||
modalState.lastContentUrl = ''; // Clear for next use
|
||||
};
|
||||
|
||||
// Load content from API
|
||||
const loadContent = async (contentUrl) => {
|
||||
try {
|
||||
console.log('Loading content from:', contentUrl);
|
||||
modalState.loading = true;
|
||||
modalState.error = false;
|
||||
modalState.errorMessage = '';
|
||||
|
||||
// Check cache first
|
||||
if (contentCache.value[contentUrl]) {
|
||||
console.log('Content found in cache for:', contentUrl);
|
||||
const cached = contentCache.value[contentUrl];
|
||||
modalState.content = cached.content;
|
||||
modalState.version = cached.version;
|
||||
modalState.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
console.log('Fetching content from API:', contentUrl);
|
||||
const response = await fetch(contentUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API response received:', {
|
||||
hasSuccess: data.success !== undefined,
|
||||
hasContent: data.content !== undefined,
|
||||
hasError: data.error !== undefined,
|
||||
contentLength: data.content ? data.content.length : 0
|
||||
});
|
||||
|
||||
// Support both response formats
|
||||
if (data.success !== undefined) {
|
||||
// New format with success property
|
||||
if (data.success) {
|
||||
modalState.content = data.content || '';
|
||||
modalState.version = data.version || '';
|
||||
} else {
|
||||
throw new Error(data.error || 'Onbekende fout bij het laden van content');
|
||||
}
|
||||
} else if (data.content !== undefined) {
|
||||
// Legacy format without success property (current privacy/terms endpoints)
|
||||
modalState.content = data.content || '';
|
||||
modalState.version = data.version || '';
|
||||
} else if (data.error) {
|
||||
// Error response format
|
||||
throw new Error(data.message || data.error || 'Er is een fout opgetreden');
|
||||
} else {
|
||||
throw new Error('Invalid response format: no content or success property found');
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
contentCache.value[contentUrl] = {
|
||||
content: modalState.content,
|
||||
version: modalState.version,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading content:', error);
|
||||
modalState.error = true;
|
||||
modalState.errorMessage = error.message || 'Er is een fout opgetreden bij het laden van de content.';
|
||||
} finally {
|
||||
modalState.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Retry loading content
|
||||
const retryLoad = async () => {
|
||||
if (modalState.lastContentUrl) {
|
||||
console.log('Retrying content load for:', modalState.lastContentUrl);
|
||||
await loadContent(modalState.lastContentUrl);
|
||||
} else {
|
||||
console.warn('No content URL available for retry');
|
||||
modalState.error = true;
|
||||
modalState.errorMessage = 'Geen content URL beschikbaar voor opnieuw proberen.';
|
||||
}
|
||||
};
|
||||
|
||||
// Clear cache (useful for development or when content updates)
|
||||
const clearCache = () => {
|
||||
contentCache.value = {};
|
||||
};
|
||||
|
||||
// Get cache size for debugging
|
||||
const getCacheInfo = () => {
|
||||
return {
|
||||
size: Object.keys(contentCache.value).length,
|
||||
entries: Object.keys(contentCache.value)
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
modalState,
|
||||
|
||||
// Methods
|
||||
showModal,
|
||||
hideModal,
|
||||
loadContent,
|
||||
retryLoad,
|
||||
clearCache,
|
||||
getCacheInfo
|
||||
};
|
||||
}
|
||||
|
||||
// Provider function to be used in the root component
|
||||
export function provideContentModal() {
|
||||
const contentModal = useContentModal();
|
||||
provide(CONTENT_MODAL_KEY, contentModal);
|
||||
return contentModal;
|
||||
}
|
||||
|
||||
// Injector function to be used in child components
|
||||
export function injectContentModal() {
|
||||
const contentModal = inject(CONTENT_MODAL_KEY);
|
||||
if (!contentModal) {
|
||||
throw new Error('useContentModal must be provided before it can be injected');
|
||||
}
|
||||
return contentModal;
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export function useComponentTranslations(componentName, originalTexts) {
|
||||
const translations = provider.registerComponent(componentName, originalTexts);
|
||||
|
||||
return {
|
||||
translations: computed(() => translations.translated),
|
||||
texts: computed(() => translations.translated),
|
||||
isLoading: computed(() => translations.isLoading),
|
||||
error: computed(() => translations.error),
|
||||
currentLanguage: provider.currentLanguage
|
||||
|
||||
@@ -33,6 +33,19 @@ active_text_color<template>
|
||||
ref="chatInput"
|
||||
class="chat-input-area"
|
||||
></chat-input>
|
||||
|
||||
<!-- Content Modal - positioned at ChatApp level -->
|
||||
<content-modal
|
||||
:show="contentModal.modalState.show"
|
||||
:title="contentModal.modalState.title"
|
||||
:content="contentModal.modalState.content"
|
||||
:version="contentModal.modalState.version"
|
||||
:loading="contentModal.modalState.loading"
|
||||
:error="contentModal.modalState.error"
|
||||
:error-message="contentModal.modalState.errorMessage"
|
||||
@close="contentModal.hideModal"
|
||||
@retry="contentModal.retryLoad"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -46,9 +59,12 @@ import MessageHistory from './MessageHistory.vue';
|
||||
import ProgressTracker from './ProgressTracker.vue';
|
||||
import LanguageSelector from './LanguageSelector.vue';
|
||||
import ChatInput from './ChatInput.vue';
|
||||
import ContentModal from './ContentModal.vue';
|
||||
|
||||
// Import language provider
|
||||
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
|
||||
// Import content modal composable
|
||||
import { provideContentModal } from '../js/composables/useContentModal.js';
|
||||
import { provide } from 'vue';
|
||||
|
||||
export default {
|
||||
@@ -60,7 +76,8 @@ export default {
|
||||
ChatMessage,
|
||||
MessageHistory,
|
||||
ProgressTracker,
|
||||
ChatInput
|
||||
ChatInput,
|
||||
ContentModal
|
||||
},
|
||||
|
||||
setup() {
|
||||
@@ -71,11 +88,15 @@ export default {
|
||||
// Creëer language provider
|
||||
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
|
||||
|
||||
// Creëer en provide content modal
|
||||
const contentModal = provideContentModal();
|
||||
|
||||
// Provide aan alle child components
|
||||
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
|
||||
|
||||
return {
|
||||
languageProvider
|
||||
languageProvider,
|
||||
contentModal
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
v-if="formData && formData.fields"
|
||||
:form-data="formData"
|
||||
:form-values="formValues"
|
||||
:api-prefix="apiPrefix"
|
||||
:is-submitting="isLoading"
|
||||
:hide-actions="true"
|
||||
@update:form-values="updateFormValues"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<dynamic-form
|
||||
:form-data="message.formData"
|
||||
:form-values="message.formValues"
|
||||
:api-prefix="apiPrefix"
|
||||
:read-only="true"
|
||||
hide-actions
|
||||
class="message-form user-form"
|
||||
|
||||
412
eveai_chat_client/static/assets/vue-components/ContentModal.vue
Normal file
412
eveai_chat_client/static/assets/vue-components/ContentModal.vue
Normal file
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<div v-if="show" class="modal-overlay" @click="handleOverlayClick">
|
||||
<div class="modal-dialog" @click.stop>
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ title }}</h4>
|
||||
<button type="button" class="modal-close" @click="closeModal" aria-label="Close">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal Body -->
|
||||
<div class="modal-body">
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Content wordt geladen...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="error-container">
|
||||
<div class="error-icon">
|
||||
<span class="material-icons">error_outline</span>
|
||||
</div>
|
||||
<h5>Fout bij het laden van content</h5>
|
||||
<p>{{ errorMessage }}</p>
|
||||
<button type="button" class="btn btn-primary" @click="retryLoad">
|
||||
Opnieuw proberen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content Display -->
|
||||
<div v-else-if="content" class="content-container">
|
||||
<div class="content-body" v-html="renderedContent"></div>
|
||||
<div v-if="version" class="content-version">
|
||||
Versie: {{ version }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="empty-container">
|
||||
<p>Geen content beschikbaar.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="closeModal">
|
||||
Sluiten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ContentModal',
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Content'
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: 'Er is een fout opgetreden bij het laden van de content.'
|
||||
}
|
||||
},
|
||||
emits: ['close', 'retry'],
|
||||
computed: {
|
||||
renderedContent() {
|
||||
if (!this.content) return '';
|
||||
|
||||
// Use marked library if available (same pattern as SideBarExplanation)
|
||||
if (typeof window.marked === 'function') {
|
||||
return window.marked(this.content);
|
||||
} else if (window.marked && typeof window.marked.parse === 'function') {
|
||||
return window.marked.parse(this.content);
|
||||
} else {
|
||||
console.warn('Marked library not available, falling back to basic parsing');
|
||||
// Fallback to basic regex-based parsing if marked is not available
|
||||
return this.content
|
||||
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||
.replace(/^\* (.*$)/gim, '<li>$1</li>')
|
||||
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
|
||||
.replace(/\*(.*)\*/gim, '<em>$1</em>')
|
||||
.replace(/\n\n/gim, '</p><p>')
|
||||
.replace(/^(?!<[h|l|p])/gim, '<p>')
|
||||
.replace(/(?<![h|l|p]>)$/gim, '</p>')
|
||||
.replace(/<p><\/p>/gim, '')
|
||||
.replace(/<p>(<h[1-6]>)/gim, '$1')
|
||||
.replace(/(<\/h[1-6]>)<\/p>/gim, '$1')
|
||||
.replace(/<p>(<li>)/gim, '<ul>$1')
|
||||
.replace(/(<\/li>)<\/p>/gim, '$1</ul>');
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.$emit('close');
|
||||
},
|
||||
handleOverlayClick() {
|
||||
// Close modal when clicking on overlay (outside the dialog)
|
||||
this.closeModal();
|
||||
},
|
||||
retryLoad() {
|
||||
this.$emit('retry');
|
||||
},
|
||||
handleEscapeKey(event) {
|
||||
if (event.key === 'Escape' && this.show) {
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Add escape key listener
|
||||
document.addEventListener('keydown', this.handleEscapeKey);
|
||||
},
|
||||
beforeUnmount() {
|
||||
// Remove escape key listener
|
||||
document.removeEventListener('keydown', this.handleEscapeKey);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Modal Overlay */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Modal Dialog */
|
||||
.modal-dialog {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
width: 80vw;
|
||||
height: min(80vh, calc(100vh - 120px));
|
||||
max-height: calc(100vh - 120px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Modal Header */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background-color: #e9ecef;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-close .material-icons {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* Modal Body */
|
||||
.modal-body {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-icon .material-icons {
|
||||
font-size: 48px;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.error-container h5 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #dc3545;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-container p {
|
||||
margin: 0 0 20px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Content Display */
|
||||
.content-container {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-body h1,
|
||||
.content-body h2,
|
||||
.content-body h3 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content-body h1 {
|
||||
font-size: 24px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.content-body h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.content-body h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content-body p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.content-body ul {
|
||||
margin: 12px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.content-body li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.content-body strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content-version {
|
||||
margin-top: 20px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Modal Footer */
|
||||
.modal-footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.modal-overlay {
|
||||
padding: 10px;
|
||||
bottom: 80px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 95vw;
|
||||
height: min(95vh, calc(100vh - 60px));
|
||||
max-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="dynamic-form-container">
|
||||
<div class="dynamic-form" :class="{ 'readonly': readOnly, 'edit': !readOnly }">
|
||||
<!-- Form header with icon and title -->
|
||||
<div v-if="formData.title || formData.name || formData.icon" class="form-header">
|
||||
<div v-if="shouldShowFormHeader" class="form-header">
|
||||
<div v-if="formData.icon" class="form-icon">
|
||||
<span class="material-symbols-outlined">{{ formData.icon }}</span>
|
||||
</div>
|
||||
@@ -19,6 +19,8 @@
|
||||
: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-terms-modal="openTermsModal"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="typeof formData.fields === 'object'">
|
||||
@@ -29,6 +31,8 @@
|
||||
:field-id="fieldId"
|
||||
:model-value="localFormValues[fieldId]"
|
||||
@update:model-value="updateFieldValue(fieldId, $event)"
|
||||
@open-privacy-modal="openPrivacyModal"
|
||||
@open-terms-modal="openTermsModal"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -68,12 +72,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormField from './FormField.vue';
|
||||
import { useIconManager } from '../js/composables/useIconManager.js';
|
||||
import { injectContentModal } from '../js/composables/useContentModal.js';
|
||||
|
||||
export default {
|
||||
name: 'DynamicForm',
|
||||
@@ -82,11 +88,14 @@ export default {
|
||||
},
|
||||
setup(props) {
|
||||
const { watchIcon } = useIconManager();
|
||||
const contentModal = injectContentModal();
|
||||
|
||||
// Watch formData.icon for automatic icon loading
|
||||
watchIcon(() => props.formData?.icon);
|
||||
|
||||
return {};
|
||||
return {
|
||||
contentModal
|
||||
};
|
||||
},
|
||||
props: {
|
||||
formData: {
|
||||
@@ -136,6 +145,10 @@ export default {
|
||||
hideActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
apiPrefix: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['submit', 'cancel', 'update:formValues'],
|
||||
@@ -195,6 +208,16 @@ export default {
|
||||
}
|
||||
|
||||
return missingFields.length === 0;
|
||||
},
|
||||
// Title display mode configuration
|
||||
titleDisplayMode() {
|
||||
console.log('Title display mode:', window.chatConfig?.form_title_display || 'Full Title');
|
||||
return window.chatConfig?.form_title_display || 'Full Title';
|
||||
},
|
||||
// Determine if form header should be shown
|
||||
shouldShowFormHeader() {
|
||||
const hasContent = this.formData.title || this.formData.name || this.formData.icon;
|
||||
return hasContent && this.titleDisplayMode !== 'No Title';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -393,6 +416,40 @@ export default {
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
},
|
||||
|
||||
// Modal handling methods
|
||||
openPrivacyModal() {
|
||||
this.loadContent('privacy');
|
||||
},
|
||||
|
||||
openTermsModal() {
|
||||
this.loadContent('terms');
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.contentModal.hideModal();
|
||||
},
|
||||
|
||||
retryLoad() {
|
||||
// Retry loading the last requested content type
|
||||
const currentTitle = this.contentModal.modalState.title.toLowerCase();
|
||||
if (currentTitle.includes('privacy')) {
|
||||
this.loadContent('privacy');
|
||||
} else if (currentTitle.includes('terms')) {
|
||||
this.loadContent('terms');
|
||||
}
|
||||
},
|
||||
|
||||
async loadContent(contentType) {
|
||||
const title = contentType === 'privacy' ? 'Privacy Statement' : 'Terms & Conditions';
|
||||
const contentUrl = `${this.apiPrefix}/${contentType}`;
|
||||
|
||||
// Use the composable to show modal and load content
|
||||
await this.contentModal.showModal({
|
||||
title: title,
|
||||
contentUrl: contentUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,7 +99,16 @@
|
||||
:required="field.required"
|
||||
style="margin-right: 8px;"
|
||||
>
|
||||
<span class="checkbox-text">{{ field.name }}</span>
|
||||
<!-- Regular checkbox label -->
|
||||
<span v-if="!isConsentField" class="checkbox-text">{{ field.name }}</span>
|
||||
<!-- Consent field with privacy and terms links -->
|
||||
<span v-else class="checkbox-text consent-text">
|
||||
{{ texts.consentPrefix }}
|
||||
<a href="#" @click="openPrivacyModal" class="consent-link">{{ texts.privacyLink }}</a>
|
||||
{{ texts.consentMiddle }}
|
||||
<a href="#" @click="openTermsModal" class="consent-link">{{ texts.termsLink }}</a>
|
||||
{{ texts.consentSuffix }}
|
||||
</span>
|
||||
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -167,6 +176,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
|
||||
|
||||
export default {
|
||||
name: 'FormField',
|
||||
props: {
|
||||
@@ -185,8 +196,57 @@ export default {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
emits: ['update:modelValue', 'open-privacy-modal', 'open-terms-modal'],
|
||||
setup() {
|
||||
// Consent text constants (English base)
|
||||
const consentTexts = {
|
||||
consentPrefix: "I agree with the",
|
||||
consentMiddle: "and",
|
||||
consentSuffix: "of AskEveAI",
|
||||
privacyLink: "privacy statement",
|
||||
termsLink: "terms and conditions"
|
||||
};
|
||||
|
||||
try {
|
||||
// Initialize translations for this component
|
||||
const { texts } = useComponentTranslations('FormField', consentTexts);
|
||||
|
||||
return {
|
||||
translatedTexts: texts,
|
||||
fallbackTexts: consentTexts
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('FormField setup(): Error in useComponentTranslations:', error);
|
||||
|
||||
// Return fallback texts if LanguageProvider fails
|
||||
return {
|
||||
translatedTexts: null,
|
||||
fallbackTexts: consentTexts
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
texts() {
|
||||
// Robust consent texts that always return valid values
|
||||
// Use translated texts if available and valid, otherwise use fallback
|
||||
if (this.translatedTexts && typeof this.translatedTexts === 'object') {
|
||||
const translated = this.translatedTexts;
|
||||
// Check if translated texts have all required properties
|
||||
if (translated.consentPrefix && translated.consentMiddle && translated.consentSuffix &&
|
||||
translated.privacyLink && translated.termsLink) {
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to English texts
|
||||
return this.fallbackTexts || {
|
||||
consentPrefix: "I agree with the",
|
||||
consentMiddle: "and",
|
||||
consentSuffix: "of AskEveAI",
|
||||
privacyLink: "privacy statement",
|
||||
termsLink: "terms and conditions"
|
||||
};
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
// Gebruik default waarde als modelValue undefined is
|
||||
@@ -239,6 +299,12 @@ export default {
|
||||
},
|
||||
description() {
|
||||
return this.field.description || '';
|
||||
},
|
||||
isConsentField() {
|
||||
// Detect consent fields by fieldId (key in dictionary) only, not by name (translated label)
|
||||
return this.field.type === 'boolean' &&
|
||||
(this.fieldId === 'consent' ||
|
||||
this.fieldId.toLowerCase().includes('consent'));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -247,6 +313,14 @@ export default {
|
||||
if (file) {
|
||||
this.value = file;
|
||||
}
|
||||
},
|
||||
openPrivacyModal(event) {
|
||||
event.preventDefault();
|
||||
this.$emit('open-privacy-modal');
|
||||
},
|
||||
openTermsModal(event) {
|
||||
event.preventDefault();
|
||||
this.$emit('open-terms-modal');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -399,6 +473,29 @@ export default {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* Consent field styling */
|
||||
.consent-text {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.consent-link {
|
||||
color: #007bff;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.consent-link:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.consent-link:focus {
|
||||
outline: 2px solid #007bff;
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.form-field {
|
||||
|
||||
@@ -34,17 +34,17 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
// Use component translations from provider
|
||||
const originalTexts = computed(() => ({
|
||||
// Use component translations from provider - fix: use reactive object instead of computed
|
||||
const originalTexts = {
|
||||
explanation: props.originalText || ''
|
||||
}));
|
||||
};
|
||||
|
||||
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
|
||||
const { texts: translations, isLoading, error, currentLanguage } = useComponentTranslations(
|
||||
'sidebar_explanation',
|
||||
originalTexts.value
|
||||
originalTexts
|
||||
);
|
||||
|
||||
const translatedText = computed(() => translations.value.explanation || props.originalText);
|
||||
const translatedText = computed(() => translations.value?.explanation || props.originalText);
|
||||
|
||||
// Render markdown content
|
||||
const renderedExplanation = computed(() => {
|
||||
@@ -61,12 +61,11 @@ const renderedExplanation = computed(() => {
|
||||
});
|
||||
|
||||
// Watch for text changes to update the provider
|
||||
watch(() => props.originalText, () => {
|
||||
// Update original texts when prop changes
|
||||
originalTexts.value = {
|
||||
explanation: props.originalText || ''
|
||||
};
|
||||
});
|
||||
watch(() => props.originalText, (newText) => {
|
||||
// Re-register component with new text if needed
|
||||
// The LanguageProvider will handle the update automatically
|
||||
console.log('SideBarExplanation: Original text changed to:', newText);
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
window.chatConfig = {
|
||||
explanation: `{{ customisation.sidebar_markdown|default('') }}`,
|
||||
progress_tracker_insights: `{{ customisation.progress_tracker_insights|default('No Information') }}`,
|
||||
form_title_display: `{{ customisation.form_title_display|default('Full Title') }}`,
|
||||
conversationId: '{{ conversation_id|default("default") }}',
|
||||
messages: {{ messages|tojson|safe }},
|
||||
settings: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from flask import Blueprint, render_template, request, session, current_app, jsonify, Response, stream_with_context
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.extensions import db
|
||||
from common.extensions import db, content_manager
|
||||
from common.models.user import Tenant, SpecialistMagicLinkTenant, TenantMake
|
||||
from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
|
||||
from common.services.interaction.specialist_services import SpecialistServices
|
||||
@@ -378,3 +378,70 @@ def translate():
|
||||
'error': f"Error translating: {str(e)}"
|
||||
}), 500
|
||||
|
||||
|
||||
@chat_bp.route('/privacy', methods=['GET'])
|
||||
def privacy_statement():
|
||||
"""
|
||||
Public AJAX endpoint for privacy statement content
|
||||
Returns JSON response suitable for modal display
|
||||
"""
|
||||
try:
|
||||
# Use content_manager to get the latest privacy content
|
||||
content_data = content_manager.read_content('privacy')
|
||||
|
||||
if not content_data:
|
||||
current_app.logger.error("Privacy statement content not found")
|
||||
return jsonify({
|
||||
'error': 'Privacy statement not available',
|
||||
'message': 'The privacy statement could not be loaded at this time.'
|
||||
}), 404
|
||||
|
||||
current_app.logger.debug(f"Content data: {content_data}")
|
||||
|
||||
# Return JSON response for AJAX consumption
|
||||
return jsonify({
|
||||
'title': 'Privacy Statement',
|
||||
'content': content_data['content'],
|
||||
'version': content_data['version'],
|
||||
'content_type': content_data['content_type']
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error loading privacy statement: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Server error',
|
||||
'message': 'An error occurred while loading the privacy statement.'
|
||||
}), 500
|
||||
|
||||
|
||||
@chat_bp.route('/terms', methods=['GET'])
|
||||
def terms_conditions():
|
||||
"""
|
||||
Public AJAX endpoint for terms & conditions content
|
||||
Returns JSON response suitable for modal display
|
||||
"""
|
||||
try:
|
||||
# Use content_manager to get the latest terms content
|
||||
content_data = content_manager.read_content('terms')
|
||||
|
||||
if not content_data:
|
||||
current_app.logger.error("Terms & conditions content not found")
|
||||
return jsonify({
|
||||
'error': 'Terms & conditions not available',
|
||||
'message': 'The terms & conditions could not be loaded at this time.'
|
||||
}), 404
|
||||
|
||||
# Return JSON response for AJAX consumption
|
||||
return jsonify({
|
||||
'title': 'Terms & Conditions',
|
||||
'content': content_data['content'],
|
||||
'version': content_data['version'],
|
||||
'content_type': content_data['content_type']
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error loading terms & conditions: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Server error',
|
||||
'message': 'An error occurred while loading the terms & conditions.'
|
||||
}), 500
|
||||
@@ -168,7 +168,7 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
||||
# We are in orientation mode, so we give a standard message, and move to rag state
|
||||
start_selection_question = TranslationServices.translate(self.tenant_id, START_SELECTION_QUESTION,
|
||||
arguments.language)
|
||||
self.flow.state.answer = f"{welcome_message}\n\n{start_selection_question}"
|
||||
self.flow.state.answer = f"{welcome_message}"
|
||||
self.flow.state.phase = "rag"
|
||||
|
||||
results = SelectionResult.create_for_type(self.type, self.type_version)
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Boolean Field Fix</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.test-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.test-result {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.form-values {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>Test Boolean Field Fix</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Basic Boolean Field</h2>
|
||||
<p>Test dat een niet-aangevinkte checkbox false retourneert in plaats van een lege string.</p>
|
||||
|
||||
<dynamic-form
|
||||
:form-data="testForm1"
|
||||
:form-values="formValues1"
|
||||
@update:form-values="formValues1 = $event"
|
||||
@submit="handleSubmit1"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
|
||||
<div class="form-values">
|
||||
<strong>Huidige waarden:</strong><br>
|
||||
{{ JSON.stringify(formValues1, null, 2) }}
|
||||
</div>
|
||||
|
||||
<div v-if="submitResult1" class="test-result" :class="submitResult1.success ? 'success' : 'error'">
|
||||
{{ submitResult1.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Required Boolean Field</h2>
|
||||
<p>Test dat required boolean velden correct valideren (false is een geldige waarde).</p>
|
||||
|
||||
<dynamic-form
|
||||
:form-data="testForm2"
|
||||
:form-values="formValues2"
|
||||
@update:form-values="formValues2 = $event"
|
||||
@submit="handleSubmit2"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
|
||||
<div class="form-values">
|
||||
<strong>Huidige waarden:</strong><br>
|
||||
{{ JSON.stringify(formValues2, null, 2) }}
|
||||
</div>
|
||||
|
||||
<div v-if="submitResult2" class="test-result" :class="submitResult2.success ? 'success' : 'error'">
|
||||
{{ submitResult2.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Mixed Form with Boolean and Other Fields</h2>
|
||||
<p>Test een formulier met zowel boolean als andere veldtypen.</p>
|
||||
|
||||
<dynamic-form
|
||||
:form-data="testForm3"
|
||||
:form-values="formValues3"
|
||||
@update:form-values="formValues3 = $event"
|
||||
@submit="handleSubmit3"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
|
||||
<div class="form-values">
|
||||
<strong>Huidige waarden:</strong><br>
|
||||
{{ JSON.stringify(formValues3, null, 2) }}
|
||||
</div>
|
||||
|
||||
<div v-if="submitResult3" class="test-result" :class="submitResult3.success ? 'success' : 'error'">
|
||||
{{ submitResult3.message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
// Import components (in real scenario these would be imported from actual files)
|
||||
// For this test, we'll create mock components that simulate the behavior
|
||||
|
||||
const FormField = {
|
||||
name: 'FormField',
|
||||
props: {
|
||||
field: { type: Object, required: true },
|
||||
fieldId: { type: String, required: true },
|
||||
modelValue: { default: null }
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
if (this.modelValue === undefined || this.modelValue === null) {
|
||||
if (this.field.type === 'boolean') {
|
||||
return this.field.default === true;
|
||||
}
|
||||
return this.field.default !== undefined ? this.field.default : '';
|
||||
}
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
// Type conversie voor boolean velden
|
||||
let processedValue = value;
|
||||
if (this.field.type === 'boolean') {
|
||||
processedValue = Boolean(value);
|
||||
}
|
||||
|
||||
if (JSON.stringify(processedValue) !== JSON.stringify(this.modelValue)) {
|
||||
this.$emit('update:modelValue', processedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
fieldType() {
|
||||
const typeMap = {
|
||||
'boolean': 'checkbox',
|
||||
'string': 'text',
|
||||
'str': 'text'
|
||||
};
|
||||
return typeMap[this.field.type] || this.field.type;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="form-field" style="margin-bottom: 15px;">
|
||||
<label v-if="fieldType !== 'checkbox'" :for="fieldId">
|
||||
{{ field.name }}
|
||||
<span v-if="field.required" style="color: red;">*</span>
|
||||
</label>
|
||||
|
||||
<input v-if="fieldType === 'text'"
|
||||
:id="fieldId"
|
||||
type="text"
|
||||
v-model="value"
|
||||
:required="field.required"
|
||||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
|
||||
<div v-if="fieldType === 'checkbox'" style="display: flex; align-items: center;">
|
||||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||||
<input type="checkbox"
|
||||
:id="fieldId"
|
||||
v-model="value"
|
||||
:required="field.required"
|
||||
style="margin-right: 8px;">
|
||||
<span>{{ field.name }}</span>
|
||||
<span v-if="field.required" style="color: red; margin-left: 2px;">*</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
const DynamicForm = {
|
||||
name: 'DynamicForm',
|
||||
components: { 'form-field': FormField },
|
||||
props: {
|
||||
formData: { type: Object, required: true },
|
||||
formValues: { type: Object, default: () => ({}) }
|
||||
},
|
||||
emits: ['submit', 'cancel', 'update:formValues'],
|
||||
data() {
|
||||
return {
|
||||
localFormValues: { ...this.formValues }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFormValid() {
|
||||
const missingFields = [];
|
||||
|
||||
this.formData.fields.forEach(field => {
|
||||
const fieldId = field.id || field.name;
|
||||
if (field.required) {
|
||||
const value = this.localFormValues[fieldId];
|
||||
if (field.type === 'boolean') {
|
||||
if (typeof value !== 'boolean') {
|
||||
missingFields.push(field.name);
|
||||
}
|
||||
} else {
|
||||
if (value === undefined || value === null ||
|
||||
(typeof value === 'string' && !value.trim())) {
|
||||
missingFields.push(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return missingFields.length === 0;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formValues: {
|
||||
handler(newValues) {
|
||||
if (JSON.stringify(newValues) !== JSON.stringify(this.localFormValues)) {
|
||||
this.localFormValues = JSON.parse(JSON.stringify(newValues));
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
localFormValues: {
|
||||
handler(newValues) {
|
||||
if (JSON.stringify(newValues) !== JSON.stringify(this.formValues)) {
|
||||
this.$emit('update:formValues', JSON.parse(JSON.stringify(newValues)));
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateFieldValue(fieldId, value) {
|
||||
const field = this.formData.fields.find(f => (f.id || f.name) === fieldId);
|
||||
|
||||
let processedValue = value;
|
||||
if (field && field.type === 'boolean') {
|
||||
processedValue = Boolean(value);
|
||||
}
|
||||
|
||||
this.localFormValues = {
|
||||
...this.localFormValues,
|
||||
[fieldId]: processedValue
|
||||
};
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$emit('submit', this.localFormValues);
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit('cancel');
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
|
||||
<h3>{{ formData.title }}</h3>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<form-field
|
||||
v-for="field in formData.fields"
|
||||
:key="field.id || field.name"
|
||||
:field="field"
|
||||
:field-id="field.id || field.name"
|
||||
:model-value="localFormValues[field.id || field.name]"
|
||||
@update:model-value="updateFieldValue(field.id || field.name, $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" @click="handleCancel" style="margin-right: 10px; padding: 8px 16px;">
|
||||
Annuleren
|
||||
</button>
|
||||
<button type="button" @click="handleSubmit" :disabled="!isFormValid"
|
||||
style="padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px;"
|
||||
:style="{ opacity: isFormValid ? 1 : 0.5 }">
|
||||
Versturen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
components: {
|
||||
'dynamic-form': DynamicForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Test 1: Basic boolean field
|
||||
testForm1: {
|
||||
title: 'Basic Boolean Test',
|
||||
fields: [
|
||||
{
|
||||
name: 'Akkoord',
|
||||
type: 'boolean',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
formValues1: {},
|
||||
submitResult1: null,
|
||||
|
||||
// Test 2: Required boolean field
|
||||
testForm2: {
|
||||
title: 'Required Boolean Test',
|
||||
fields: [
|
||||
{
|
||||
name: 'Verplichte Checkbox',
|
||||
type: 'boolean',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
formValues2: {},
|
||||
submitResult2: null,
|
||||
|
||||
// Test 3: Mixed form
|
||||
testForm3: {
|
||||
title: 'Mixed Form Test',
|
||||
fields: [
|
||||
{
|
||||
name: 'Naam',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'Nieuwsbrief',
|
||||
type: 'boolean',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'Voorwaarden',
|
||||
type: 'boolean',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
formValues3: {},
|
||||
submitResult3: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit1(values) {
|
||||
const akkoordValue = values['Akkoord'];
|
||||
const isBoolean = typeof akkoordValue === 'boolean';
|
||||
const isCorrectValue = akkoordValue === false || akkoordValue === true;
|
||||
|
||||
this.submitResult1 = {
|
||||
success: isBoolean && isCorrectValue,
|
||||
message: isBoolean && isCorrectValue
|
||||
? `✅ SUCCESS: Boolean field retourneert correct ${akkoordValue} (type: ${typeof akkoordValue})`
|
||||
: `❌ FAILED: Boolean field retourneert ${akkoordValue} (type: ${typeof akkoordValue})`
|
||||
};
|
||||
},
|
||||
|
||||
handleSubmit2(values) {
|
||||
const checkboxValue = values['Verplichte Checkbox'];
|
||||
const isBoolean = typeof checkboxValue === 'boolean';
|
||||
|
||||
this.submitResult2 = {
|
||||
success: isBoolean,
|
||||
message: isBoolean
|
||||
? `✅ SUCCESS: Required boolean field retourneert correct ${checkboxValue} (type: ${typeof checkboxValue})`
|
||||
: `❌ FAILED: Required boolean field retourneert ${checkboxValue} (type: ${typeof checkboxValue})`
|
||||
};
|
||||
},
|
||||
|
||||
handleSubmit3(values) {
|
||||
const naam = values['Naam'];
|
||||
const nieuwsbrief = values['Nieuwsbrief'];
|
||||
const voorwaarden = values['Voorwaarden'];
|
||||
|
||||
const naamOk = typeof naam === 'string' && naam.length > 0;
|
||||
const nieuwsbriefOk = typeof nieuwsbrief === 'boolean';
|
||||
const voorwaardenOk = typeof voorwaarden === 'boolean';
|
||||
|
||||
const allOk = naamOk && nieuwsbriefOk && voorwaardenOk;
|
||||
|
||||
this.submitResult3 = {
|
||||
success: allOk,
|
||||
message: allOk
|
||||
? `✅ SUCCESS: Alle velden correct - Naam: "${naam}", Nieuwsbrief: ${nieuwsbrief}, Voorwaarden: ${voorwaarden}`
|
||||
: `❌ FAILED: Problemen met velden - Naam: ${naam} (${typeof naam}), Nieuwsbrief: ${nieuwsbrief} (${typeof nieuwsbrief}), Voorwaarden: ${voorwaarden} (${typeof voorwaarden})`
|
||||
};
|
||||
},
|
||||
|
||||
handleCancel() {
|
||||
console.log('Form cancelled');
|
||||
}
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for Chat Client Customisation corrections.
|
||||
This script tests the specific corrections made:
|
||||
1. Percentage range handling (-100 to +100)
|
||||
2. Color manipulation accuracy
|
||||
3. Template integration with corrected values
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the project root to the Python path
|
||||
sys.path.insert(0, '/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD')
|
||||
|
||||
from common.utils.chat_utils import (
|
||||
get_default_chat_customisation,
|
||||
hex_to_rgb,
|
||||
adjust_color_brightness,
|
||||
get_base_background_color
|
||||
)
|
||||
|
||||
def test_percentage_range_corrections():
|
||||
"""Test that the full -100 to +100 percentage range works correctly."""
|
||||
print("Testing percentage range corrections (-100 to +100)...")
|
||||
|
||||
base_color = '#808080' # Medium gray
|
||||
|
||||
print("\n1. Testing extreme percentage values:")
|
||||
extreme_tests = [
|
||||
(-100, "Maximum darken"),
|
||||
(-75, "Heavy darken"),
|
||||
(-50, "Medium darken"),
|
||||
(-25, "Light darken"),
|
||||
(0, "No change"),
|
||||
(25, "Light lighten"),
|
||||
(50, "Medium lighten"),
|
||||
(75, "Heavy lighten"),
|
||||
(100, "Maximum lighten")
|
||||
]
|
||||
|
||||
for percentage, description in extreme_tests:
|
||||
result = adjust_color_brightness(base_color, percentage)
|
||||
print(f" {percentage:4d}% ({description:15s}): {result}")
|
||||
|
||||
print("\n2. Testing edge cases:")
|
||||
edge_cases = [
|
||||
('#000000', -100, "Black with max darken"),
|
||||
('#000000', 100, "Black with max lighten"),
|
||||
('#ffffff', -100, "White with max darken"),
|
||||
('#ffffff', 100, "White with max lighten"),
|
||||
]
|
||||
|
||||
for color, percentage, description in edge_cases:
|
||||
result = adjust_color_brightness(color, percentage)
|
||||
print(f" {description}: {result}")
|
||||
|
||||
def test_customisation_with_extended_range():
|
||||
"""Test customisation system with extended percentage range."""
|
||||
print("\n\nTesting customisation system with extended range...")
|
||||
|
||||
# Test with extreme values
|
||||
custom_options = {
|
||||
'history_background': 80,
|
||||
'history_user_message_background': -90,
|
||||
'history_ai_message_background': 60,
|
||||
'active_background_color': '#2196f3',
|
||||
'active_text_color': '#ffffff',
|
||||
'markdown_text_color': '#e0e0e0'
|
||||
}
|
||||
|
||||
customisation = get_default_chat_customisation(custom_options)
|
||||
|
||||
print("\n1. Customisation with extreme percentage values:")
|
||||
for key, value in custom_options.items():
|
||||
print(f" {key}: {customisation[key]}")
|
||||
|
||||
def test_template_simulation():
|
||||
"""Simulate template rendering with corrected values."""
|
||||
print("\n\nSimulating template rendering with corrections...")
|
||||
|
||||
customisation = get_default_chat_customisation({
|
||||
'history_background': 75,
|
||||
'history_user_message_background': -85,
|
||||
'history_ai_message_background': 45,
|
||||
'markdown_text_color': '#f0f0f0'
|
||||
})
|
||||
|
||||
print("\n1. Simulated CSS custom properties with corrected values:")
|
||||
base_bg = get_base_background_color()
|
||||
|
||||
# Simulate template rendering
|
||||
history_bg = adjust_color_brightness(base_bg, customisation['history_background'])
|
||||
user_msg_bg = adjust_color_brightness('#000000', customisation['history_user_message_background'])
|
||||
ai_msg_bg = adjust_color_brightness('#ffffff', customisation['history_ai_message_background'])
|
||||
|
||||
print(f" --history-background: {history_bg}")
|
||||
print(f" --history-user-message-background: {user_msg_bg}")
|
||||
print(f" --history-ai-message-background: {ai_msg_bg}")
|
||||
print(f" --markdown-text-color: {customisation['markdown_text_color']}")
|
||||
|
||||
def test_color_accuracy():
|
||||
"""Test color manipulation accuracy with mathematical verification."""
|
||||
print("\n\nTesting color manipulation accuracy...")
|
||||
|
||||
print("\n1. Mathematical verification of color calculations:")
|
||||
test_color = '#808080' # RGB(128, 128, 128)
|
||||
|
||||
# Test 50% lighten: should move halfway to white
|
||||
result_50_light = adjust_color_brightness(test_color, 50)
|
||||
print(f" 50% lighten of {test_color}: {result_50_light}")
|
||||
print(f" Expected: approximately rgba(192, 192, 192, 0.9)")
|
||||
|
||||
# Test 50% darken: should move halfway to black
|
||||
result_50_dark = adjust_color_brightness(test_color, -50)
|
||||
print(f" 50% darken of {test_color}: {result_50_dark}")
|
||||
print(f" Expected: approximately rgba(64, 64, 64, 0.9)")
|
||||
|
||||
# Test 100% lighten: should be white
|
||||
result_100_light = adjust_color_brightness(test_color, 100)
|
||||
print(f" 100% lighten of {test_color}: {result_100_light}")
|
||||
print(f" Expected: rgba(255, 255, 255, 0.9)")
|
||||
|
||||
# Test 100% darken: should be black
|
||||
result_100_dark = adjust_color_brightness(test_color, -100)
|
||||
print(f" 100% darken of {test_color}: {result_100_dark}")
|
||||
print(f" Expected: rgba(0, 0, 0, 0.9)")
|
||||
|
||||
def main():
|
||||
"""Run all correction tests."""
|
||||
print("=" * 70)
|
||||
print("CHAT CLIENT CUSTOMISATION CORRECTIONS TEST")
|
||||
print("=" * 70)
|
||||
|
||||
try:
|
||||
test_percentage_range_corrections()
|
||||
test_customisation_with_extended_range()
|
||||
test_template_simulation()
|
||||
test_color_accuracy()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ ALL CORRECTION TESTS COMPLETED SUCCESSFULLY!")
|
||||
print("The following corrections have been verified:")
|
||||
print(" 1. ✅ Percentage range now supports -100 to +100")
|
||||
print(" 2. ✅ Color manipulation works correctly across full range")
|
||||
print(" 3. ✅ Sidebar markdown text color uses --markdown-text-color")
|
||||
print(" 4. ✅ Chat messages area uses --history-background")
|
||||
print("=" * 70)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ TEST FAILED: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -1,244 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to validate the English base language implementation
|
||||
Tests component-specific translations and initial language checks
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.append(str(project_root))
|
||||
|
||||
def test_file_contains(file_path, search_terms, description):
|
||||
"""Test if a file contains specific terms"""
|
||||
full_path = project_root / file_path
|
||||
if not full_path.exists():
|
||||
print(f"❌ FAIL - {description}: File not found - {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
all_found = all(term in content for term in search_terms)
|
||||
status = "✅ PASS" if all_found else "❌ FAIL"
|
||||
print(f"{status} - {description}")
|
||||
|
||||
if not all_found:
|
||||
missing = [term for term in search_terms if term not in content]
|
||||
print(f" Missing terms: {missing}")
|
||||
|
||||
return all_found
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL - {description}: Error reading file - {e}")
|
||||
return False
|
||||
|
||||
def test_file_not_contains(file_path, search_terms, description):
|
||||
"""Test if a file does NOT contain specific terms"""
|
||||
full_path = project_root / file_path
|
||||
if not full_path.exists():
|
||||
print(f"❌ FAIL - {description}: File not found - {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
none_found = not any(term in content for term in search_terms)
|
||||
status = "✅ PASS" if none_found else "❌ FAIL"
|
||||
print(f"{status} - {description}")
|
||||
|
||||
if not none_found:
|
||||
found = [term for term in search_terms if term in content]
|
||||
print(f" Found unwanted terms: {found}")
|
||||
|
||||
return none_found
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL - {description}: Error reading file - {e}")
|
||||
return False
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests for the English base language implementation"""
|
||||
print("🧪 Testing English Base Language Implementation")
|
||||
print("=" * 60)
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
# Test 1: LanguageProvider uses English as default
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["initialLanguage = 'en'", "Central language management system"],
|
||||
"LanguageProvider uses English as default language"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 2: LanguageProvider has component-specific translation function
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["translateComponentTexts", "component-specific", "targetLanguage === 'en'"],
|
||||
"LanguageProvider has component-specific translation logic"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 3: LanguageProvider forces initial translation for non-English languages
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["currentLanguage.value !== 'en'", "needs initial translation", "translateComponentTexts(componentName, currentLanguage.value)"],
|
||||
"LanguageProvider forces initial translation for non-English languages"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 4: ProgressTracker uses English original texts
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ProgressTracker.vue",
|
||||
["Error while processing", "Processing completed", "Processing..."],
|
||||
"ProgressTracker uses English original texts"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 5: ProgressTracker does NOT use Dutch texts
|
||||
total_tests += 1
|
||||
if test_file_not_contains("eveai_chat_client/static/assets/vue-components/ProgressTracker.vue",
|
||||
["Fout bij verwerking", "Verwerking voltooid", "Bezig met redeneren"],
|
||||
"ProgressTracker does not use Dutch texts"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 6: ChatMessage uses English original texts
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ChatMessage.vue",
|
||||
["Try again", "Copy", "Timestamp", "An error occurred while processing"],
|
||||
"ChatMessage uses English original texts"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 7: ChatMessage does NOT use Dutch texts
|
||||
total_tests += 1
|
||||
if test_file_not_contains("eveai_chat_client/static/assets/vue-components/ChatMessage.vue",
|
||||
["Opnieuw proberen", "Kopiëren", "Tijdstempel", "Er is een fout opgetreden"],
|
||||
"ChatMessage does not use Dutch texts"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 8: SideBarExplanation uses English as default language
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue",
|
||||
["default: 'en'", "Translating..."],
|
||||
"SideBarExplanation uses English as default language"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 9: LanguageProvider handles English base language correctly
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["For English, use original texts", "no translation needed", "Start with original English texts"],
|
||||
"LanguageProvider handles English base language correctly"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 10: LanguageProvider has proper logging for debugging
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["console.log", "Registering component", "needs initial translation", "Successfully translated"],
|
||||
"LanguageProvider has proper logging for debugging"):
|
||||
tests_passed += 1
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"🧪 Test Results: {tests_passed}/{total_tests} tests passed")
|
||||
|
||||
if tests_passed == total_tests:
|
||||
print("🎉 All tests passed! English base language implementation looks good.")
|
||||
print("\n✅ Key improvements implemented:")
|
||||
print(" - English is now the base language for all components")
|
||||
print(" - Component-specific caching prevents cross-contamination")
|
||||
print(" - Initial language translation works at component registration")
|
||||
print(" - No fallback to Dutch - English is the source language")
|
||||
print(" - Proper logging for debugging translation issues")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {total_tests - tests_passed} tests failed. Please review the implementation.")
|
||||
return False
|
||||
|
||||
def check_cache_logic():
|
||||
"""Check the cache logic implementation"""
|
||||
print("\n🔍 Checking Cache Logic Implementation")
|
||||
print("=" * 50)
|
||||
|
||||
issues_found = 0
|
||||
|
||||
# Check if global cache is still being used incorrectly
|
||||
language_provider_path = project_root / "eveai_chat_client/static/assets/js/services/LanguageProvider.js"
|
||||
if language_provider_path.exists():
|
||||
content = language_provider_path.read_text(encoding='utf-8')
|
||||
|
||||
# Check for component-specific cache
|
||||
if 'componentTranslations[componentName]' in content:
|
||||
print("✅ Component-specific cache implementation found")
|
||||
else:
|
||||
print("⚠️ Component-specific cache not properly implemented")
|
||||
issues_found += 1
|
||||
|
||||
# Check for proper English base language handling
|
||||
if "targetLanguage === 'en'" in content:
|
||||
print("✅ English base language handling found")
|
||||
else:
|
||||
print("⚠️ English base language handling not found")
|
||||
issues_found += 1
|
||||
|
||||
# Check for initial translation logic
|
||||
if "needs initial translation" in content:
|
||||
print("✅ Initial translation logic found")
|
||||
else:
|
||||
print("⚠️ Initial translation logic not found")
|
||||
issues_found += 1
|
||||
|
||||
if issues_found == 0:
|
||||
print("✅ Cache logic implementation looks good")
|
||||
else:
|
||||
print(f"⚠️ {issues_found} issue(s) found in cache logic")
|
||||
|
||||
return issues_found == 0
|
||||
|
||||
def check_startup_behavior():
|
||||
"""Check startup behavior for different language scenarios"""
|
||||
print("\n🔍 Checking Startup Behavior")
|
||||
print("=" * 50)
|
||||
|
||||
print("📋 Expected startup behavior:")
|
||||
print(" 1. App starts with chatConfig.language (e.g., 'nl', 'fr', 'de')")
|
||||
print(" 2. Components register with English original texts")
|
||||
print(" 3. If initial language ≠ 'en', automatic translation is triggered")
|
||||
print(" 4. Cache is populated with correct translations for initial language")
|
||||
print(" 5. No fallback to Dutch - English is always the source")
|
||||
|
||||
# Check chat.html for language configuration
|
||||
chat_html_path = project_root / "eveai_chat_client/templates/chat.html"
|
||||
if chat_html_path.exists():
|
||||
content = chat_html_path.read_text(encoding='utf-8')
|
||||
if "language: '{{ session.magic_link.specialist_args.language|default(" in content:
|
||||
print("✅ Dynamic language configuration found in chat.html")
|
||||
if '|default("nl")' in content:
|
||||
print("⚠️ Default language in chat.html is still 'nl' - consider changing to 'en'")
|
||||
elif '|default("en")' in content:
|
||||
print("✅ Default language in chat.html is 'en'")
|
||||
else:
|
||||
print("⚠️ Language configuration not found in chat.html")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 English Base Language Implementation Test Suite")
|
||||
print("Testing component-specific translations with English as base language")
|
||||
print("=" * 70)
|
||||
|
||||
# Run main tests
|
||||
success = run_tests()
|
||||
|
||||
# Check cache logic
|
||||
cache_ok = check_cache_logic()
|
||||
|
||||
# Check startup behavior
|
||||
startup_ok = check_startup_behavior()
|
||||
|
||||
if success and cache_ok and startup_ok:
|
||||
print("\n✅ Implementation validation successful!")
|
||||
print("🎯 The system now uses English as the base language with proper component-specific caching.")
|
||||
print("🔧 Initial language translation should work correctly at startup.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ Implementation validation failed!")
|
||||
print("Please review and fix the issues before testing in browser.")
|
||||
sys.exit(1)
|
||||
@@ -1,219 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to validate the Language Provider implementation
|
||||
Tests the provider/inject pattern for translation management
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.append(str(project_root))
|
||||
|
||||
def test_file_exists(file_path, description):
|
||||
"""Test if a file exists"""
|
||||
full_path = project_root / file_path
|
||||
exists = full_path.exists()
|
||||
status = "✅ PASS" if exists else "❌ FAIL"
|
||||
print(f"{status} - {description}: {file_path}")
|
||||
return exists
|
||||
|
||||
def test_file_contains(file_path, search_terms, description):
|
||||
"""Test if a file contains specific terms"""
|
||||
full_path = project_root / file_path
|
||||
if not full_path.exists():
|
||||
print(f"❌ FAIL - {description}: File not found - {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
all_found = all(term in content for term in search_terms)
|
||||
status = "✅ PASS" if all_found else "❌ FAIL"
|
||||
print(f"{status} - {description}")
|
||||
|
||||
if not all_found:
|
||||
missing = [term for term in search_terms if term not in content]
|
||||
print(f" Missing terms: {missing}")
|
||||
|
||||
return all_found
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL - {description}: Error reading file - {e}")
|
||||
return False
|
||||
|
||||
def test_javascript_syntax(file_path, description):
|
||||
"""Test JavaScript syntax using Node.js if available"""
|
||||
full_path = project_root / file_path
|
||||
if not full_path.exists():
|
||||
print(f"❌ FAIL - {description}: File not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Try to check syntax with node if available
|
||||
result = subprocess.run(['node', '-c', str(full_path)],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == 0:
|
||||
print(f"✅ PASS - {description}: JavaScript syntax valid")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ FAIL - {description}: JavaScript syntax error")
|
||||
print(f" Error: {result.stderr}")
|
||||
return False
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
print(f"⚠️ SKIP - {description}: Node.js not available for syntax check")
|
||||
return True # Don't fail if Node.js is not available
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests for the Language Provider implementation"""
|
||||
print("🧪 Testing Language Provider Implementation")
|
||||
print("=" * 50)
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
# Test 1: LanguageProvider service exists
|
||||
total_tests += 1
|
||||
if test_file_exists("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
"LanguageProvider service file exists"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 2: LanguageProvider contains required exports
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
["createLanguageProvider", "useLanguageProvider", "useComponentTranslations", "LANGUAGE_PROVIDER_KEY"],
|
||||
"LanguageProvider exports required functions"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 3: ChatApp.vue provides the language provider
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ChatApp.vue",
|
||||
["createLanguageProvider", "provide", "LANGUAGE_PROVIDER_KEY"],
|
||||
"ChatApp.vue provides language provider"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 4: ProgressTracker uses component translations
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ProgressTracker.vue",
|
||||
["useComponentTranslations", "progress_tracker"],
|
||||
"ProgressTracker uses component translations"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 5: SideBarExplanation uses component translations
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue",
|
||||
["useComponentTranslations", "sidebar_explanation"],
|
||||
"SideBarExplanation uses component translations"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 6: ChatMessage uses component translations
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ChatMessage.vue",
|
||||
["useComponentTranslations", "chat_message"],
|
||||
"ChatMessage uses component translations"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 7: LanguageSelector optionally uses provider
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/LanguageSelector.vue",
|
||||
["useLanguageProvider", "providerLanguage", "effectiveCurrentLanguage"],
|
||||
"LanguageSelector optionally uses provider"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 8: Check JavaScript syntax of LanguageProvider
|
||||
total_tests += 1
|
||||
if test_javascript_syntax("eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
"LanguageProvider JavaScript syntax"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 9: Verify old event-based code is removed from ProgressTracker
|
||||
total_tests += 1
|
||||
progress_tracker_path = "eveai_chat_client/static/assets/vue-components/ProgressTracker.vue"
|
||||
full_path = project_root / progress_tracker_path
|
||||
if full_path.exists():
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
old_patterns = ["handleLanguageChange", "language-changed", "translateConstants"]
|
||||
has_old_code = any(pattern in content for pattern in old_patterns)
|
||||
if not has_old_code:
|
||||
print("✅ PASS - ProgressTracker: Old event-based translation code removed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("❌ FAIL - ProgressTracker: Still contains old event-based translation code")
|
||||
else:
|
||||
print("❌ FAIL - ProgressTracker: File not found")
|
||||
|
||||
# Test 10: Verify old event-based code is removed from ChatMessage
|
||||
total_tests += 1
|
||||
chat_message_path = "eveai_chat_client/static/assets/vue-components/ChatMessage.vue"
|
||||
full_path = project_root / chat_message_path
|
||||
if full_path.exists():
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
old_patterns = ["handleLanguageChange", "language-changed"]
|
||||
has_old_code = any(pattern in content for pattern in old_patterns)
|
||||
if not has_old_code:
|
||||
print("✅ PASS - ChatMessage: Old event-based translation code removed")
|
||||
tests_passed += 1
|
||||
else:
|
||||
print("❌ FAIL - ChatMessage: Still contains old event-based translation code")
|
||||
else:
|
||||
print("❌ FAIL - ChatMessage: File not found")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"🧪 Test Results: {tests_passed}/{total_tests} tests passed")
|
||||
|
||||
if tests_passed == total_tests:
|
||||
print("🎉 All tests passed! Language Provider implementation looks good.")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {total_tests - tests_passed} tests failed. Please review the implementation.")
|
||||
return False
|
||||
|
||||
def check_implementation_completeness():
|
||||
"""Check if the implementation is complete"""
|
||||
print("\n🔍 Checking Implementation Completeness")
|
||||
print("=" * 50)
|
||||
|
||||
# Check if all required files exist
|
||||
required_files = [
|
||||
"eveai_chat_client/static/assets/js/services/LanguageProvider.js",
|
||||
"eveai_chat_client/static/assets/vue-components/ChatApp.vue",
|
||||
"eveai_chat_client/static/assets/vue-components/ProgressTracker.vue",
|
||||
"eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue",
|
||||
"eveai_chat_client/static/assets/vue-components/ChatMessage.vue",
|
||||
"eveai_chat_client/static/assets/vue-components/LanguageSelector.vue"
|
||||
]
|
||||
|
||||
all_exist = True
|
||||
for file_path in required_files:
|
||||
full_path = project_root / file_path
|
||||
if full_path.exists():
|
||||
print(f"✅ {file_path}")
|
||||
else:
|
||||
print(f"❌ {file_path} - MISSING")
|
||||
all_exist = False
|
||||
|
||||
return all_exist
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 Language Provider Implementation Test Suite")
|
||||
print("Testing provider/inject pattern for translation management")
|
||||
print("=" * 60)
|
||||
|
||||
# Check completeness first
|
||||
if not check_implementation_completeness():
|
||||
print("\n❌ Implementation incomplete. Please ensure all files exist.")
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests
|
||||
success = run_tests()
|
||||
|
||||
if success:
|
||||
print("\n✅ Implementation validation successful!")
|
||||
print("The provider/inject pattern should resolve timing issues.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ Implementation validation failed!")
|
||||
print("Please review and fix the issues before proceeding.")
|
||||
sys.exit(1)
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to validate the Sidebar LanguageProvider fix
|
||||
Tests that both SideBar and ChatApp have access to LanguageProvider
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.append(str(project_root))
|
||||
|
||||
def test_file_contains(file_path, search_terms, description):
|
||||
"""Test if a file contains specific terms"""
|
||||
full_path = project_root / file_path
|
||||
if not full_path.exists():
|
||||
print(f"❌ FAIL - {description}: File not found - {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
content = full_path.read_text(encoding='utf-8')
|
||||
all_found = all(term in content for term in search_terms)
|
||||
status = "✅ PASS" if all_found else "❌ FAIL"
|
||||
print(f"{status} - {description}")
|
||||
|
||||
if not all_found:
|
||||
missing = [term for term in search_terms if term not in content]
|
||||
print(f" Missing terms: {missing}")
|
||||
|
||||
return all_found
|
||||
except Exception as e:
|
||||
print(f"❌ FAIL - {description}: Error reading file - {e}")
|
||||
return False
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests for the Sidebar LanguageProvider fix"""
|
||||
print("🧪 Testing Sidebar LanguageProvider Fix")
|
||||
print("=" * 50)
|
||||
|
||||
tests_passed = 0
|
||||
total_tests = 0
|
||||
|
||||
# Test 1: chat-client.js imports LanguageProvider
|
||||
total_tests += 1
|
||||
if test_file_contains("nginx/frontend_src/js/chat-client.js",
|
||||
["import { createLanguageProvider, LANGUAGE_PROVIDER_KEY }"],
|
||||
"chat-client.js imports LanguageProvider"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 2: initializeSidebar creates and provides LanguageProvider
|
||||
total_tests += 1
|
||||
if test_file_contains("nginx/frontend_src/js/chat-client.js",
|
||||
["createLanguageProvider(initialLanguage, apiPrefix)", "app.provide(LANGUAGE_PROVIDER_KEY, languageProvider)"],
|
||||
"initializeSidebar creates and provides LanguageProvider"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 3: SideBar app listens to language-changed events
|
||||
total_tests += 1
|
||||
if test_file_contains("nginx/frontend_src/js/chat-client.js",
|
||||
["languageChangeHandler", "document.addEventListener('language-changed'", "languageProvider.setLanguage"],
|
||||
"SideBar app listens to language-changed events"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 4: SideBarExplanation uses useComponentTranslations
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue",
|
||||
["useComponentTranslations", "sidebar_explanation"],
|
||||
"SideBarExplanation uses useComponentTranslations"):
|
||||
tests_passed += 1
|
||||
|
||||
# Test 5: ChatApp still provides LanguageProvider
|
||||
total_tests += 1
|
||||
if test_file_contains("eveai_chat_client/static/assets/vue-components/ChatApp.vue",
|
||||
["createLanguageProvider", "provide(LANGUAGE_PROVIDER_KEY"],
|
||||
"ChatApp still provides LanguageProvider"):
|
||||
tests_passed += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"🧪 Test Results: {tests_passed}/{total_tests} tests passed")
|
||||
|
||||
if tests_passed == total_tests:
|
||||
print("🎉 All tests passed! Sidebar LanguageProvider fix looks good.")
|
||||
print("\n✅ Expected fixes:")
|
||||
print(" - SideBarExplanation should no longer throw 'useLanguageProvider must be used within a LanguageProvider' error")
|
||||
print(" - Sidebar explanation text should be visible and translatable")
|
||||
print(" - Both sidebar and chat translations should work correctly")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {total_tests - tests_passed} tests failed. Please review the implementation.")
|
||||
return False
|
||||
|
||||
def check_potential_issues():
|
||||
"""Check for potential issues with the implementation"""
|
||||
print("\n🔍 Checking for Potential Issues")
|
||||
print("=" * 50)
|
||||
|
||||
issues_found = 0
|
||||
|
||||
# Check if both apps create separate LanguageProvider instances
|
||||
chat_client_path = project_root / "nginx/frontend_src/js/chat-client.js"
|
||||
if chat_client_path.exists():
|
||||
content = chat_client_path.read_text(encoding='utf-8')
|
||||
provider_creations = content.count('createLanguageProvider(')
|
||||
if provider_creations >= 1:
|
||||
print(f"✅ Found {provider_creations} LanguageProvider creation(s) in chat-client.js")
|
||||
else:
|
||||
print("⚠️ No LanguageProvider creation found in chat-client.js")
|
||||
issues_found += 1
|
||||
|
||||
# Check if ChatApp also creates LanguageProvider
|
||||
chatapp_path = project_root / "eveai_chat_client/static/assets/vue-components/ChatApp.vue"
|
||||
if chatapp_path.exists():
|
||||
content = chatapp_path.read_text(encoding='utf-8')
|
||||
if 'createLanguageProvider(' in content:
|
||||
print("✅ ChatApp creates LanguageProvider")
|
||||
else:
|
||||
print("⚠️ ChatApp doesn't create LanguageProvider")
|
||||
issues_found += 1
|
||||
|
||||
if issues_found == 0:
|
||||
print("✅ No potential issues detected")
|
||||
else:
|
||||
print(f"⚠️ {issues_found} potential issue(s) detected")
|
||||
|
||||
return issues_found == 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 Sidebar LanguageProvider Fix Test Suite")
|
||||
print("Testing fix for 'useLanguageProvider must be used within a LanguageProvider' error")
|
||||
print("=" * 70)
|
||||
|
||||
# Run tests
|
||||
success = run_tests()
|
||||
|
||||
# Check for potential issues
|
||||
no_issues = check_potential_issues()
|
||||
|
||||
if success and no_issues:
|
||||
print("\n✅ Implementation validation successful!")
|
||||
print("The sidebar should now have access to LanguageProvider and translations should work.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ Implementation validation failed!")
|
||||
print("Please review and fix the issues before testing in browser.")
|
||||
sys.exit(1)
|
||||
@@ -1,231 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the SSE stream fix for ProgressTracker.
|
||||
This script tests that:
|
||||
1. apiPrefix prop flows correctly from ChatApp → ChatInput → ChatMessage → ProgressTracker
|
||||
2. The SSE URL is constructed correctly with the apiPrefix
|
||||
3. Both MessageHistory sticky area and ChatInput active area work properly
|
||||
4. No 404 errors occur when accessing task progress endpoints
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
def test_api_prefix_prop_flow():
|
||||
"""Test that apiPrefix prop flows correctly through component hierarchy"""
|
||||
|
||||
print("🔍 Testing apiPrefix prop flow...")
|
||||
|
||||
# Test ChatApp.vue passes apiPrefix to ChatInput
|
||||
chatapp_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatApp.vue")
|
||||
|
||||
with open(chatapp_path, 'r', encoding='utf-8') as f:
|
||||
chatapp_content = f.read()
|
||||
|
||||
if ':api-prefix="apiPrefix"' in chatapp_content:
|
||||
print("✅ ChatApp passes apiPrefix to ChatInput")
|
||||
else:
|
||||
print("❌ ChatApp does not pass apiPrefix to ChatInput")
|
||||
return False
|
||||
|
||||
# Test ChatInput.vue receives and passes apiPrefix to ChatMessage
|
||||
chatinput_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatInput.vue")
|
||||
|
||||
with open(chatinput_path, 'r', encoding='utf-8') as f:
|
||||
chatinput_content = f.read()
|
||||
|
||||
# Check ChatInput has apiPrefix prop
|
||||
if 'apiPrefix: {' in chatinput_content:
|
||||
print("✅ ChatInput defines apiPrefix prop")
|
||||
else:
|
||||
print("❌ ChatInput missing apiPrefix prop")
|
||||
return False
|
||||
|
||||
# Check ChatInput passes apiPrefix to ChatMessage
|
||||
if ':api-prefix="apiPrefix"' in chatinput_content:
|
||||
print("✅ ChatInput passes apiPrefix to ChatMessage")
|
||||
else:
|
||||
print("❌ ChatInput does not pass apiPrefix to ChatMessage")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_progress_tracker_sse_url_construction():
|
||||
"""Test that ProgressTracker constructs SSE URLs correctly"""
|
||||
|
||||
print("\n🔍 Testing ProgressTracker SSE URL construction...")
|
||||
|
||||
progresstracker_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ProgressTracker.vue")
|
||||
|
||||
with open(progresstracker_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check that ProgressTracker uses apiPrefix in URL construction
|
||||
if 'const sseUrl = `${baseUrl}${this.apiPrefix}/api/task_progress/${this.taskId}`;' in content:
|
||||
print("✅ ProgressTracker constructs SSE URL with apiPrefix")
|
||||
else:
|
||||
print("❌ ProgressTracker does not use apiPrefix in URL construction")
|
||||
return False
|
||||
|
||||
# Check that ProgressTracker has apiPrefix prop
|
||||
if 'apiPrefix: {' in content:
|
||||
print("✅ ProgressTracker defines apiPrefix prop")
|
||||
else:
|
||||
print("❌ ProgressTracker missing apiPrefix prop")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_chatmessage_prop_passing():
|
||||
"""Test that ChatMessage passes apiPrefix to ProgressTracker"""
|
||||
|
||||
print("\n🔍 Testing ChatMessage prop passing...")
|
||||
|
||||
chatmessage_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatMessage.vue")
|
||||
|
||||
with open(chatmessage_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check that ChatMessage has apiPrefix prop
|
||||
if 'apiPrefix: {' in content:
|
||||
print("✅ ChatMessage defines apiPrefix prop")
|
||||
else:
|
||||
print("❌ ChatMessage missing apiPrefix prop")
|
||||
return False
|
||||
|
||||
# Check that ChatMessage passes apiPrefix to ProgressTracker
|
||||
if ':api-prefix="apiPrefix"' in content:
|
||||
print("✅ ChatMessage passes apiPrefix to ProgressTracker")
|
||||
else:
|
||||
print("❌ ChatMessage does not pass apiPrefix to ProgressTracker")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_backwards_compatibility():
|
||||
"""Test that MessageHistory sticky area still works"""
|
||||
|
||||
print("\n🔍 Testing backwards compatibility...")
|
||||
|
||||
messagehistory_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/MessageHistory.vue")
|
||||
|
||||
with open(messagehistory_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check that MessageHistory still has sticky area
|
||||
if 'sticky-ai-area' in content:
|
||||
print("✅ MessageHistory sticky area preserved")
|
||||
else:
|
||||
print("❌ MessageHistory sticky area removed")
|
||||
return False
|
||||
|
||||
# Check that MessageHistory passes apiPrefix to ChatMessage
|
||||
if ':api-prefix="apiPrefix"' in content:
|
||||
print("✅ MessageHistory passes apiPrefix to ChatMessage")
|
||||
else:
|
||||
print("❌ MessageHistory does not pass apiPrefix to ChatMessage")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_expected_url_format():
|
||||
"""Test that the expected URL format will be generated"""
|
||||
|
||||
print("\n🔍 Testing expected URL format...")
|
||||
|
||||
# Based on the error message, we expect URLs like:
|
||||
# http://macstudio.ask-eve-ai-local.com/chat-client/chat/api/task_progress/616b3e7c-da47-4255-a900-68f9eb68e94f
|
||||
|
||||
# The apiPrefix should be '/chat-client/chat' based on chat.html template
|
||||
chat_template_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/templates/chat.html")
|
||||
|
||||
with open(chat_template_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check that apiPrefix is set correctly in chat.html
|
||||
if 'apiPrefix: \'{{ request.headers.get("X-Forwarded-Prefix", "") }}/chat\'' in content:
|
||||
print("✅ apiPrefix correctly configured in chat.html template")
|
||||
print(" Expected format: /chat-client/chat/api/task_progress/{task_id}")
|
||||
else:
|
||||
print("❌ apiPrefix not correctly configured in chat.html template")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_component_integration():
|
||||
"""Test that all components are properly integrated"""
|
||||
|
||||
print("\n🔍 Testing component integration...")
|
||||
|
||||
# Test that ChatInput imports ChatMessage
|
||||
chatinput_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatInput.vue")
|
||||
|
||||
with open(chatinput_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "import ChatMessage from './ChatMessage.vue';" in content:
|
||||
print("✅ ChatInput imports ChatMessage")
|
||||
else:
|
||||
print("❌ ChatInput does not import ChatMessage")
|
||||
return False
|
||||
|
||||
if "'chat-message': ChatMessage" in content:
|
||||
print("✅ ChatInput registers ChatMessage component")
|
||||
else:
|
||||
print("❌ ChatInput does not register ChatMessage component")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
|
||||
print("🚀 Starting SSE Stream Fix Tests")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
("API Prefix Prop Flow", test_api_prefix_prop_flow),
|
||||
("ProgressTracker SSE URL Construction", test_progress_tracker_sse_url_construction),
|
||||
("ChatMessage Prop Passing", test_chatmessage_prop_passing),
|
||||
("Backwards Compatibility", test_backwards_compatibility),
|
||||
("Expected URL Format", test_expected_url_format),
|
||||
("Component Integration", test_component_integration)
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
print(f"✅ {test_name} - PASSED")
|
||||
else:
|
||||
print(f"❌ {test_name} - FAILED")
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} - ERROR: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"📊 Test Results: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All tests passed! SSE stream fix is working correctly.")
|
||||
print("\n📋 Fix Summary:")
|
||||
print("• apiPrefix now flows correctly: ChatApp → ChatInput → ChatMessage → ProgressTracker")
|
||||
print("• ProgressTracker will construct correct SSE URLs with /chat-client/chat prefix")
|
||||
print("• Both MessageHistory sticky area and ChatInput active area work properly")
|
||||
print("• No more 404 errors expected for task progress endpoints")
|
||||
print("\n🔧 Changes Made:")
|
||||
print("• Added :api-prefix=\"apiPrefix\" prop to ChatInput in ChatApp.vue")
|
||||
print("• ChatInput already had correct apiPrefix prop definition and passing")
|
||||
print("• All components in the chain now receive the apiPrefix correctly")
|
||||
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)
|
||||
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the translation implementation in eveai_chat_client.
|
||||
|
||||
This script checks:
|
||||
1. ProgressTracker.vue has proper translation setup
|
||||
2. MessageHistory.vue has correct historical message exclusion logic
|
||||
3. Translation composable is properly imported
|
||||
4. Caching mechanism is implemented
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
def check_file_exists(filepath):
|
||||
"""Check if file exists and return True/False"""
|
||||
return os.path.exists(filepath)
|
||||
|
||||
def read_file_content(filepath):
|
||||
"""Read file content safely"""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading {filepath}: {e}")
|
||||
return None
|
||||
|
||||
def test_progress_tracker_implementation():
|
||||
"""Test ProgressTracker.vue implementation"""
|
||||
print("🔍 Testing ProgressTracker.vue implementation...")
|
||||
|
||||
filepath = "/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ProgressTracker.vue"
|
||||
|
||||
if not check_file_exists(filepath):
|
||||
print("❌ ProgressTracker.vue not found")
|
||||
return False
|
||||
|
||||
content = read_file_content(filepath)
|
||||
if not content:
|
||||
return False
|
||||
|
||||
checks = [
|
||||
("Translation composable import", r"import.*useTranslationClient.*from.*useTranslation"),
|
||||
("Cache object", r"const statusTextCache = \{\}"),
|
||||
("Translated status texts data", r"translatedStatusTexts:\s*\{"),
|
||||
("Error text computed property", r"errorText\(\)\s*\{"),
|
||||
("Completed text computed property", r"completedText\(\)\s*\{"),
|
||||
("Processing text computed property", r"processingText\(\)\s*\{"),
|
||||
("Template uses computed properties", r"\{\{\s*errorText\s*\}\}"),
|
||||
("Language change handler", r"handleLanguageChange\(newLanguage\)"),
|
||||
("Cache check logic", r"if \(statusTextCache\[newLanguage\]\)"),
|
||||
("Backend translation calls", r"await this\.translateSafe"),
|
||||
("Event listener setup", r"document\.addEventListener\('language-changed'"),
|
||||
("Event listener cleanup", r"document\.removeEventListener\('language-changed'")
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for check_name, pattern in checks:
|
||||
if re.search(pattern, content, re.MULTILINE | re.DOTALL):
|
||||
print(f" ✅ {check_name}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ {check_name}")
|
||||
|
||||
print(f"ProgressTracker.vue: {passed}/{len(checks)} checks passed")
|
||||
return passed == len(checks)
|
||||
|
||||
def test_message_history_implementation():
|
||||
"""Test MessageHistory.vue implementation"""
|
||||
print("\n🔍 Testing MessageHistory.vue implementation...")
|
||||
|
||||
filepath = "/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/MessageHistory.vue"
|
||||
|
||||
if not check_file_exists(filepath):
|
||||
print("❌ MessageHistory.vue not found")
|
||||
return False
|
||||
|
||||
content = read_file_content(filepath)
|
||||
if not content:
|
||||
return False
|
||||
|
||||
checks = [
|
||||
("Translation composable import", r"import.*useTranslationClient.*from.*useTranslation"),
|
||||
("Setup function with translateSafe", r"setup\(\)\s*\{.*translateSafe.*\}"),
|
||||
("Historical message exclusion", r"if \(this\.messages\.length === 1.*sender === 'ai'\)"),
|
||||
("Original content check", r"if \(firstMessage\.originalContent\)"),
|
||||
("Translation with fallback", r"fallbackText:\s*firstMessage\.originalContent"),
|
||||
("Event listener setup", r"document\.addEventListener\('language-changed'"),
|
||||
("Event listener cleanup", r"document\.removeEventListener\('language-changed'")
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for check_name, pattern in checks:
|
||||
if re.search(pattern, content, re.MULTILINE | re.DOTALL):
|
||||
print(f" ✅ {check_name}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ {check_name}")
|
||||
|
||||
print(f"MessageHistory.vue: {passed}/{len(checks)} checks passed")
|
||||
return passed == len(checks)
|
||||
|
||||
def test_chat_message_original_content():
|
||||
"""Test ChatMessage.vue original content storage"""
|
||||
print("\n🔍 Testing ChatMessage.vue original content storage...")
|
||||
|
||||
filepath = "/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatMessage.vue"
|
||||
|
||||
if not check_file_exists(filepath):
|
||||
print("❌ ChatMessage.vue not found")
|
||||
return False
|
||||
|
||||
content = read_file_content(filepath)
|
||||
if not content:
|
||||
return False
|
||||
|
||||
checks = [
|
||||
("Original content storage", r"this\.message\.originalContent = this\.message\.content"),
|
||||
("AI message check", r"this\.message\.sender === 'ai'"),
|
||||
("Original content condition", r"!this\.message\.originalContent")
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for check_name, pattern in checks:
|
||||
if re.search(pattern, content, re.MULTILINE):
|
||||
print(f" ✅ {check_name}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ {check_name}")
|
||||
|
||||
print(f"ChatMessage.vue: {passed}/{len(checks)} checks passed")
|
||||
return passed == len(checks)
|
||||
|
||||
def test_translation_composable():
|
||||
"""Test translation composable exists"""
|
||||
print("\n🔍 Testing translation composable...")
|
||||
|
||||
filepath = "/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/js/composables/useTranslation.js"
|
||||
|
||||
if not check_file_exists(filepath):
|
||||
print("❌ useTranslation.js not found")
|
||||
return False
|
||||
|
||||
content = read_file_content(filepath)
|
||||
if not content:
|
||||
return False
|
||||
|
||||
checks = [
|
||||
("useTranslationClient export", r"export function useTranslationClient"),
|
||||
("translateSafe function", r"const translateSafe = async"),
|
||||
("Backend API call", r"await translate\("),
|
||||
("Fallback handling", r"fallbackText")
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for check_name, pattern in checks:
|
||||
if re.search(pattern, content, re.MULTILINE):
|
||||
print(f" ✅ {check_name}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ {check_name}")
|
||||
|
||||
print(f"useTranslation.js: {passed}/{len(checks)} checks passed")
|
||||
return passed == len(checks)
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("🚀 Starting translation implementation tests...\n")
|
||||
|
||||
tests = [
|
||||
test_progress_tracker_implementation,
|
||||
test_message_history_implementation,
|
||||
test_chat_message_original_content,
|
||||
test_translation_composable
|
||||
]
|
||||
|
||||
results = []
|
||||
for test in tests:
|
||||
results.append(test())
|
||||
|
||||
print(f"\n📊 Test Results:")
|
||||
print(f"Passed: {sum(results)}/{len(results)} tests")
|
||||
|
||||
if all(results):
|
||||
print("🎉 All tests passed! Translation implementation is complete.")
|
||||
return 0
|
||||
else:
|
||||
print("❌ Some tests failed. Please review the implementation.")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,80 +0,0 @@
|
||||
// Verification script to demonstrate the boolean field fix
|
||||
// This simulates the behavior of the updated FormField component
|
||||
|
||||
console.log('=== Boolean Field Fix Verification ===\n');
|
||||
|
||||
// Simulate the type conversion logic from FormField.vue
|
||||
function processFieldValue(value, fieldType) {
|
||||
let processedValue = value;
|
||||
if (fieldType === 'boolean') {
|
||||
// This is the key fix: convert all values to proper boolean
|
||||
processedValue = Boolean(value);
|
||||
}
|
||||
return processedValue;
|
||||
}
|
||||
|
||||
// Test scenarios
|
||||
const testCases = [
|
||||
{ description: 'Unchecked checkbox (empty string)', value: '', fieldType: 'boolean' },
|
||||
{ description: 'Checked checkbox (true)', value: true, fieldType: 'boolean' },
|
||||
{ description: 'Unchecked checkbox (false)', value: false, fieldType: 'boolean' },
|
||||
{ description: 'Null value', value: null, fieldType: 'boolean' },
|
||||
{ description: 'Undefined value', value: undefined, fieldType: 'boolean' },
|
||||
{ description: 'String field (should not be affected)', value: '', fieldType: 'string' },
|
||||
{ description: 'String field with value', value: 'test', fieldType: 'string' }
|
||||
];
|
||||
|
||||
console.log('Testing type conversion logic:\n');
|
||||
|
||||
testCases.forEach((testCase, index) => {
|
||||
const result = processFieldValue(testCase.value, testCase.fieldType);
|
||||
const originalType = typeof testCase.value;
|
||||
const resultType = typeof result;
|
||||
|
||||
console.log(`Test ${index + 1}: ${testCase.description}`);
|
||||
console.log(` Input: ${testCase.value} (${originalType})`);
|
||||
console.log(` Output: ${result} (${resultType})`);
|
||||
|
||||
// Verify the fix
|
||||
if (testCase.fieldType === 'boolean') {
|
||||
const isCorrect = typeof result === 'boolean';
|
||||
console.log(` ✅ ${isCorrect ? 'PASS' : 'FAIL'}: Boolean field returns boolean type`);
|
||||
|
||||
// Special check for empty string (the main issue)
|
||||
if (testCase.value === '') {
|
||||
const isFixed = result === false;
|
||||
console.log(` ✅ ${isFixed ? 'PASS' : 'FAIL'}: Empty string converts to false`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// Simulate validation logic from DynamicForm.vue
|
||||
function validateRequiredBooleanField(value) {
|
||||
// New validation logic: for boolean fields, check if it's actually a boolean
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
||||
console.log('Testing validation logic for required boolean fields:\n');
|
||||
|
||||
const validationTests = [
|
||||
{ description: 'Required boolean field with false value', value: false },
|
||||
{ description: 'Required boolean field with true value', value: true },
|
||||
{ description: 'Required boolean field with empty string (should fail)', value: '' },
|
||||
{ description: 'Required boolean field with null (should fail)', value: null }
|
||||
];
|
||||
|
||||
validationTests.forEach((test, index) => {
|
||||
const isValid = validateRequiredBooleanField(test.value);
|
||||
console.log(`Validation Test ${index + 1}: ${test.description}`);
|
||||
console.log(` Value: ${test.value} (${typeof test.value})`);
|
||||
console.log(` Valid: ${isValid ? 'YES' : 'NO'}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('=== Summary ===');
|
||||
console.log('✅ Boolean fields now return proper boolean values (true/false)');
|
||||
console.log('✅ Empty strings from unchecked checkboxes are converted to false');
|
||||
console.log('✅ Required boolean field validation accepts false as valid');
|
||||
console.log('✅ Type conversion is applied at both FormField and DynamicForm levels');
|
||||
console.log('\nThe boolean field issue has been successfully resolved!');
|
||||
Reference in New Issue
Block a user