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_start_color': '#f5f7fa',
|
||||||
'gradient_end_color': '#c3cfe2',
|
'gradient_end_color': '#c3cfe2',
|
||||||
'progress_tracker_insights': 'No Information',
|
'progress_tracker_insights': 'No Information',
|
||||||
|
'form_title_display': 'Full Title',
|
||||||
'active_background_color': '#ffffff',
|
'active_background_color': '#ffffff',
|
||||||
'active_text_color': '#212529',
|
'active_text_color': '#212529',
|
||||||
'history_background': 10,
|
'history_background': 10,
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import logging
|
|||||||
from packaging import version
|
from packaging import version
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class ContentManager:
|
class ContentManager:
|
||||||
def __init__(self, app=None):
|
def __init__(self, app=None):
|
||||||
self.app = app
|
self.app = app
|
||||||
@@ -16,10 +14,10 @@ class ContentManager:
|
|||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
# Controleer of het pad bestaat
|
# Controleer of het pad bestaat
|
||||||
if not os.path.exists(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']}")
|
# logger.warning(f"Content directory not found at: {app.config['CONTENT_DIR']}")
|
||||||
else:
|
# else:
|
||||||
logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
|
# logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
|
||||||
|
|
||||||
def get_content_path(self, content_type, major_minor=None, patch=None):
|
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)
|
content_path = os.path.join(self.app.config['CONTENT_DIR'], content_type)
|
||||||
|
|
||||||
if not os.path.exists(content_path):
|
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
|
return None
|
||||||
|
|
||||||
# Als geen major_minor opgegeven, vind de hoogste
|
# Als geen major_minor opgegeven, vind de hoogste
|
||||||
if not major_minor:
|
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:
|
if not available_versions:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -81,16 +79,19 @@ class ContentManager:
|
|||||||
|
|
||||||
# Nu we major_minor hebben, zoek de hoogste patch
|
# Nu we major_minor hebben, zoek de hoogste patch
|
||||||
major_minor_path = os.path.join(content_path, major_minor)
|
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):
|
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
|
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 = []
|
version_files = []
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
mm, p = self._parse_version(file)
|
mm, p = self._parse_version(file)
|
||||||
|
current_app.logger.debug(f"File: {file}, mm: {mm}, p: {p}")
|
||||||
if mm == major_minor and p:
|
if mm == major_minor and p:
|
||||||
version_files.append((mm, p, f"{mm}.{p}"))
|
version_files.append((mm, p, f"{mm}.{p}"))
|
||||||
|
|
||||||
@@ -99,10 +100,12 @@ class ContentManager:
|
|||||||
|
|
||||||
# Sorteer op patch nummer
|
# Sorteer op patch nummer
|
||||||
version_files.sort(key=lambda v: int(v[1]))
|
version_files.sort(key=lambda v: int(v[1]))
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Latest version: {version_files[-1]}")
|
||||||
return version_files[-1]
|
return version_files[-1]
|
||||||
|
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
def read_content(self, content_type, major_minor=None, patch=None):
|
def read_content(self, content_type, major_minor=None, patch=None):
|
||||||
@@ -125,11 +128,12 @@ class ContentManager:
|
|||||||
} of None bij fout
|
} of None bij fout
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
current_app.logger.debug(f"Reading content {content_type}")
|
||||||
# Als geen versie opgegeven, vind de laatste
|
# Als geen versie opgegeven, vind de laatste
|
||||||
if not major_minor:
|
if not major_minor:
|
||||||
version_info = self.get_latest_version(content_type)
|
version_info = self.get_latest_version(content_type)
|
||||||
if not version_info:
|
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
|
return None
|
||||||
|
|
||||||
major_minor, patch, full_version = version_info
|
major_minor, patch, full_version = version_info
|
||||||
@@ -138,7 +142,7 @@ class ContentManager:
|
|||||||
elif not patch:
|
elif not patch:
|
||||||
version_info = self.get_latest_version(content_type, major_minor)
|
version_info = self.get_latest_version(content_type, major_minor)
|
||||||
if not version_info:
|
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
|
return None
|
||||||
|
|
||||||
major_minor, patch, full_version = version_info
|
major_minor, patch, full_version = version_info
|
||||||
@@ -147,14 +151,17 @@ class ContentManager:
|
|||||||
|
|
||||||
# Nu hebben we major_minor en patch, lees het bestand
|
# Nu hebben we major_minor en patch, lees het bestand
|
||||||
file_path = self.get_content_path(content_type, major_minor, patch)
|
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):
|
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
|
return None
|
||||||
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Content read: {content}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'content': content,
|
'content': content,
|
||||||
'version': full_version,
|
'version': full_version,
|
||||||
@@ -162,7 +169,7 @@ class ContentManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
def list_content_types(self):
|
def list_content_types(self):
|
||||||
@@ -171,7 +178,7 @@ class ContentManager:
|
|||||||
return [d for d in os.listdir(self.app.config['CONTENT_DIR'])
|
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))]
|
if os.path.isdir(os.path.join(self.app.config['CONTENT_DIR'], d))]
|
||||||
except Exception as e:
|
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 []
|
return []
|
||||||
|
|
||||||
def list_versions(self, content_type):
|
def list_versions(self, content_type):
|
||||||
@@ -211,5 +218,5 @@ class ContentManager:
|
|||||||
return versions
|
return versions
|
||||||
|
|
||||||
except Exception as e:
|
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 []
|
return []
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ configuration:
|
|||||||
allowed_values: ["No Information", "Active Interaction Only", "All Interactions"]
|
allowed_values: ["No Information", "Active Interaction Only", "All Interactions"]
|
||||||
default: "No Information"
|
default: "No Information"
|
||||||
required: true
|
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:
|
active_background_color:
|
||||||
name: "Active Interaction Background Color"
|
name: "Active Interaction Background Color"
|
||||||
description: "Primary 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);
|
const translations = provider.registerComponent(componentName, originalTexts);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
translations: computed(() => translations.translated),
|
texts: computed(() => translations.translated),
|
||||||
isLoading: computed(() => translations.isLoading),
|
isLoading: computed(() => translations.isLoading),
|
||||||
error: computed(() => translations.error),
|
error: computed(() => translations.error),
|
||||||
currentLanguage: provider.currentLanguage
|
currentLanguage: provider.currentLanguage
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ active_text_color<template>
|
|||||||
ref="chatInput"
|
ref="chatInput"
|
||||||
class="chat-input-area"
|
class="chat-input-area"
|
||||||
></chat-input>
|
></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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -46,9 +59,12 @@ import MessageHistory from './MessageHistory.vue';
|
|||||||
import ProgressTracker from './ProgressTracker.vue';
|
import ProgressTracker from './ProgressTracker.vue';
|
||||||
import LanguageSelector from './LanguageSelector.vue';
|
import LanguageSelector from './LanguageSelector.vue';
|
||||||
import ChatInput from './ChatInput.vue';
|
import ChatInput from './ChatInput.vue';
|
||||||
|
import ContentModal from './ContentModal.vue';
|
||||||
|
|
||||||
// Import language provider
|
// Import language provider
|
||||||
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
|
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';
|
import { provide } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -60,7 +76,8 @@ export default {
|
|||||||
ChatMessage,
|
ChatMessage,
|
||||||
MessageHistory,
|
MessageHistory,
|
||||||
ProgressTracker,
|
ProgressTracker,
|
||||||
ChatInput
|
ChatInput,
|
||||||
|
ContentModal
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@@ -71,11 +88,15 @@ export default {
|
|||||||
// Creëer language provider
|
// Creëer language provider
|
||||||
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
|
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
|
||||||
|
|
||||||
|
// Creëer en provide content modal
|
||||||
|
const contentModal = provideContentModal();
|
||||||
|
|
||||||
// Provide aan alle child components
|
// Provide aan alle child components
|
||||||
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
|
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
languageProvider
|
languageProvider,
|
||||||
|
contentModal
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
v-if="formData && formData.fields"
|
v-if="formData && formData.fields"
|
||||||
:form-data="formData"
|
:form-data="formData"
|
||||||
:form-values="formValues"
|
:form-values="formValues"
|
||||||
|
:api-prefix="apiPrefix"
|
||||||
:is-submitting="isLoading"
|
:is-submitting="isLoading"
|
||||||
:hide-actions="true"
|
:hide-actions="true"
|
||||||
@update:form-values="updateFormValues"
|
@update:form-values="updateFormValues"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<dynamic-form
|
<dynamic-form
|
||||||
:form-data="message.formData"
|
:form-data="message.formData"
|
||||||
:form-values="message.formValues"
|
:form-values="message.formValues"
|
||||||
|
:api-prefix="apiPrefix"
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
hide-actions
|
hide-actions
|
||||||
class="message-form user-form"
|
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-container">
|
||||||
<div class="dynamic-form" :class="{ 'readonly': readOnly, 'edit': !readOnly }">
|
<div class="dynamic-form" :class="{ 'readonly': readOnly, 'edit': !readOnly }">
|
||||||
<!-- Form header with icon and title -->
|
<!-- 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">
|
<div v-if="formData.icon" class="form-icon">
|
||||||
<span class="material-symbols-outlined">{{ formData.icon }}</span>
|
<span class="material-symbols-outlined">{{ formData.icon }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
:field-id="field.id || field.name"
|
:field-id="field.id || field.name"
|
||||||
:model-value="localFormValues[field.id || field.name]"
|
:model-value="localFormValues[field.id || field.name]"
|
||||||
@update:model-value="updateFieldValue(field.id || field.name, $event)"
|
@update:model-value="updateFieldValue(field.id || field.name, $event)"
|
||||||
|
@open-privacy-modal="openPrivacyModal"
|
||||||
|
@open-terms-modal="openTermsModal"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="typeof formData.fields === 'object'">
|
<template v-else-if="typeof formData.fields === 'object'">
|
||||||
@@ -29,6 +31,8 @@
|
|||||||
:field-id="fieldId"
|
:field-id="fieldId"
|
||||||
:model-value="localFormValues[fieldId]"
|
:model-value="localFormValues[fieldId]"
|
||||||
@update:model-value="updateFieldValue(fieldId, $event)"
|
@update:model-value="updateFieldValue(fieldId, $event)"
|
||||||
|
@open-privacy-modal="openPrivacyModal"
|
||||||
|
@open-terms-modal="openTermsModal"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,12 +72,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormField from './FormField.vue';
|
import FormField from './FormField.vue';
|
||||||
import { useIconManager } from '../js/composables/useIconManager.js';
|
import { useIconManager } from '../js/composables/useIconManager.js';
|
||||||
|
import { injectContentModal } from '../js/composables/useContentModal.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DynamicForm',
|
name: 'DynamicForm',
|
||||||
@@ -82,11 +88,14 @@ export default {
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { watchIcon } = useIconManager();
|
const { watchIcon } = useIconManager();
|
||||||
|
const contentModal = injectContentModal();
|
||||||
|
|
||||||
// Watch formData.icon for automatic icon loading
|
// Watch formData.icon for automatic icon loading
|
||||||
watchIcon(() => props.formData?.icon);
|
watchIcon(() => props.formData?.icon);
|
||||||
|
|
||||||
return {};
|
return {
|
||||||
|
contentModal
|
||||||
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
formData: {
|
formData: {
|
||||||
@@ -136,6 +145,10 @@ export default {
|
|||||||
hideActions: {
|
hideActions: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
apiPrefix: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['submit', 'cancel', 'update:formValues'],
|
emits: ['submit', 'cancel', 'update:formValues'],
|
||||||
@@ -195,6 +208,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return missingFields.length === 0;
|
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: {
|
watch: {
|
||||||
@@ -393,6 +416,40 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return value.toString();
|
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"
|
:required="field.required"
|
||||||
style="margin-right: 8px;"
|
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>
|
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,6 +176,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormField',
|
name: 'FormField',
|
||||||
props: {
|
props: {
|
||||||
@@ -185,8 +196,57 @@ export default {
|
|||||||
default: null
|
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: {
|
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: {
|
value: {
|
||||||
get() {
|
get() {
|
||||||
// Gebruik default waarde als modelValue undefined is
|
// Gebruik default waarde als modelValue undefined is
|
||||||
@@ -239,6 +299,12 @@ export default {
|
|||||||
},
|
},
|
||||||
description() {
|
description() {
|
||||||
return this.field.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: {
|
methods: {
|
||||||
@@ -247,6 +313,14 @@ export default {
|
|||||||
if (file) {
|
if (file) {
|
||||||
this.value = 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;
|
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 */
|
/* Responsive adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.form-field {
|
.form-field {
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use component translations from provider
|
// Use component translations from provider - fix: use reactive object instead of computed
|
||||||
const originalTexts = computed(() => ({
|
const originalTexts = {
|
||||||
explanation: props.originalText || ''
|
explanation: props.originalText || ''
|
||||||
}));
|
};
|
||||||
|
|
||||||
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
|
const { texts: translations, isLoading, error, currentLanguage } = useComponentTranslations(
|
||||||
'sidebar_explanation',
|
'sidebar_explanation',
|
||||||
originalTexts.value
|
originalTexts
|
||||||
);
|
);
|
||||||
|
|
||||||
const translatedText = computed(() => translations.value.explanation || props.originalText);
|
const translatedText = computed(() => translations.value?.explanation || props.originalText);
|
||||||
|
|
||||||
// Render markdown content
|
// Render markdown content
|
||||||
const renderedExplanation = computed(() => {
|
const renderedExplanation = computed(() => {
|
||||||
@@ -61,12 +61,11 @@ const renderedExplanation = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Watch for text changes to update the provider
|
// Watch for text changes to update the provider
|
||||||
watch(() => props.originalText, () => {
|
watch(() => props.originalText, (newText) => {
|
||||||
// Update original texts when prop changes
|
// Re-register component with new text if needed
|
||||||
originalTexts.value = {
|
// The LanguageProvider will handle the update automatically
|
||||||
explanation: props.originalText || ''
|
console.log('SideBarExplanation: Original text changed to:', newText);
|
||||||
};
|
}, { immediate: true });
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
window.chatConfig = {
|
window.chatConfig = {
|
||||||
explanation: `{{ customisation.sidebar_markdown|default('') }}`,
|
explanation: `{{ customisation.sidebar_markdown|default('') }}`,
|
||||||
progress_tracker_insights: `{{ customisation.progress_tracker_insights|default('No Information') }}`,
|
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") }}',
|
conversationId: '{{ conversation_id|default("default") }}',
|
||||||
messages: {{ messages|tojson|safe }},
|
messages: {{ messages|tojson|safe }},
|
||||||
settings: {
|
settings: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from flask import Blueprint, render_template, request, session, current_app, jsonify, Response, stream_with_context
|
from flask import Blueprint, render_template, request, session, current_app, jsonify, Response, stream_with_context
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
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.user import Tenant, SpecialistMagicLinkTenant, TenantMake
|
||||||
from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
|
from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
|
||||||
from common.services.interaction.specialist_services import SpecialistServices
|
from common.services.interaction.specialist_services import SpecialistServices
|
||||||
@@ -378,3 +378,70 @@ def translate():
|
|||||||
'error': f"Error translating: {str(e)}"
|
'error': f"Error translating: {str(e)}"
|
||||||
}), 500
|
}), 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
|
# 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,
|
start_selection_question = TranslationServices.translate(self.tenant_id, START_SELECTION_QUESTION,
|
||||||
arguments.language)
|
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"
|
self.flow.state.phase = "rag"
|
||||||
|
|
||||||
results = SelectionResult.create_for_type(self.type, self.type_version)
|
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