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:
Josako
2025-07-28 21:47:56 +02:00
parent ef138462d9
commit 5e81595622
28 changed files with 1609 additions and 2271 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -29,6 +29,7 @@ def get_default_chat_customisation(tenant_customisation=None):
'gradient_start_color': '#f5f7fa',
'gradient_end_color': '#c3cfe2',
'progress_tracker_insights': 'No Information',
'form_title_display': 'Full Title',
'active_background_color': '#ffffff',
'active_text_color': '#212529',
'history_background': 10,

View File

@@ -4,8 +4,6 @@ import logging
from packaging import version
from flask import current_app
logger = logging.getLogger(__name__)
class ContentManager:
def __init__(self, app=None):
self.app = app
@@ -16,10 +14,10 @@ class ContentManager:
self.app = app
# Controleer of het pad bestaat
if not os.path.exists(app.config['CONTENT_DIR']):
logger.warning(f"Content directory not found at: {app.config['CONTENT_DIR']}")
else:
logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
# if not os.path.exists(app.config['CONTENT_DIR']):
# logger.warning(f"Content directory not found at: {app.config['CONTENT_DIR']}")
# else:
# logger.info(f"Content directory configured at: {app.config['CONTENT_DIR']}")
def get_content_path(self, content_type, major_minor=None, patch=None):
"""
@@ -66,12 +64,12 @@ class ContentManager:
content_path = os.path.join(self.app.config['CONTENT_DIR'], content_type)
if not os.path.exists(content_path):
logger.error(f"Content path does not exist: {content_path}")
current_app.logger.error(f"Content path does not exist: {content_path}")
return None
# Als geen major_minor opgegeven, vind de hoogste
if not major_minor:
available_versions = os.listdir(content_path)
available_versions = [f for f in os.listdir(content_path) if not f.startswith('.')]
if not available_versions:
return None
@@ -81,16 +79,19 @@ class ContentManager:
# Nu we major_minor hebben, zoek de hoogste patch
major_minor_path = os.path.join(content_path, major_minor)
current_app.logger.debug(f"Major/Minor path: {major_minor_path}")
if not os.path.exists(major_minor_path):
logger.error(f"Version path does not exist: {major_minor_path}")
current_app.logger.error(f"Version path does not exist: {major_minor_path}")
return None
files = os.listdir(major_minor_path)
files = [f for f in os.listdir(major_minor_path) if not f.startswith('.')]
current_app.logger.debug(f"Files in version path: {files}")
version_files = []
for file in files:
mm, p = self._parse_version(file)
current_app.logger.debug(f"File: {file}, mm: {mm}, p: {p}")
if mm == major_minor and p:
version_files.append((mm, p, f"{mm}.{p}"))
@@ -99,10 +100,12 @@ class ContentManager:
# Sorteer op patch nummer
version_files.sort(key=lambda v: int(v[1]))
current_app.logger.debug(f"Latest version: {version_files[-1]}")
return version_files[-1]
except Exception as e:
logger.error(f"Error finding latest version for {content_type}: {str(e)}")
current_app.logger.error(f"Error finding latest version for {content_type}: {str(e)}")
return None
def read_content(self, content_type, major_minor=None, patch=None):
@@ -125,11 +128,12 @@ class ContentManager:
} of None bij fout
"""
try:
current_app.logger.debug(f"Reading content {content_type}")
# Als geen versie opgegeven, vind de laatste
if not major_minor:
version_info = self.get_latest_version(content_type)
if not version_info:
logger.error(f"No versions found for {content_type}")
current_app.logger.error(f"No versions found for {content_type}")
return None
major_minor, patch, full_version = version_info
@@ -138,7 +142,7 @@ class ContentManager:
elif not patch:
version_info = self.get_latest_version(content_type, major_minor)
if not version_info:
logger.error(f"No versions found for {content_type} {major_minor}")
current_app.logger.error(f"No versions found for {content_type} {major_minor}")
return None
major_minor, patch, full_version = version_info
@@ -147,14 +151,17 @@ class ContentManager:
# Nu hebben we major_minor en patch, lees het bestand
file_path = self.get_content_path(content_type, major_minor, patch)
current_app.logger.debug(f"Content File path: {file_path}")
if not os.path.exists(file_path):
logger.error(f"Content file does not exist: {file_path}")
current_app.logger.error(f"Content file does not exist: {file_path}")
return None
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
current_app.logger.debug(f"Content read: {content}")
return {
'content': content,
'version': full_version,
@@ -162,7 +169,7 @@ class ContentManager:
}
except Exception as e:
logger.error(f"Error reading content {content_type} {major_minor}.{patch}: {str(e)}")
current_app.logger.error(f"Error reading content {content_type} {major_minor}.{patch}: {str(e)}")
return None
def list_content_types(self):
@@ -171,7 +178,7 @@ class ContentManager:
return [d for d in os.listdir(self.app.config['CONTENT_DIR'])
if os.path.isdir(os.path.join(self.app.config['CONTENT_DIR'], d))]
except Exception as e:
logger.error(f"Error listing content types: {str(e)}")
current_app.logger.error(f"Error listing content types: {str(e)}")
return []
def list_versions(self, content_type):
@@ -211,5 +218,5 @@ class ContentManager:
return versions
except Exception as e:
logger.error(f"Error listing versions for {content_type}: {str(e)}")
current_app.logger.error(f"Error listing versions for {content_type}: {str(e)}")
return []

View File

@@ -43,6 +43,13 @@ configuration:
allowed_values: ["No Information", "Active Interaction Only", "All Interactions"]
default: "No Information"
required: true
form_title_display:
name: "Form Title Display"
description: Level of information shown for the Form Title
type: "enum"
allowed_values: ["No Title", "Full Title"]
default: "Full Title"
required: true
active_background_color:
name: "Active Interaction Background Color"
description: "Primary Color"

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -211,7 +211,7 @@ export function useComponentTranslations(componentName, originalTexts) {
const translations = provider.registerComponent(componentName, originalTexts);
return {
translations: computed(() => translations.translated),
texts: computed(() => translations.translated),
isLoading: computed(() => translations.isLoading),
error: computed(() => translations.error),
currentLanguage: provider.currentLanguage

View File

@@ -33,6 +33,19 @@ active_text_color<template>
ref="chatInput"
class="chat-input-area"
></chat-input>
<!-- Content Modal - positioned at ChatApp level -->
<content-modal
:show="contentModal.modalState.show"
:title="contentModal.modalState.title"
:content="contentModal.modalState.content"
:version="contentModal.modalState.version"
:loading="contentModal.modalState.loading"
:error="contentModal.modalState.error"
:error-message="contentModal.modalState.errorMessage"
@close="contentModal.hideModal"
@retry="contentModal.retryLoad"
/>
</div>
</template>
@@ -46,9 +59,12 @@ import MessageHistory from './MessageHistory.vue';
import ProgressTracker from './ProgressTracker.vue';
import LanguageSelector from './LanguageSelector.vue';
import ChatInput from './ChatInput.vue';
import ContentModal from './ContentModal.vue';
// Import language provider
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
// Import content modal composable
import { provideContentModal } from '../js/composables/useContentModal.js';
import { provide } from 'vue';
export default {
@@ -60,7 +76,8 @@ export default {
ChatMessage,
MessageHistory,
ProgressTracker,
ChatInput
ChatInput,
ContentModal
},
setup() {
@@ -71,11 +88,15 @@ export default {
// Creëer language provider
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
// Creëer en provide content modal
const contentModal = provideContentModal();
// Provide aan alle child components
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
return {
languageProvider
languageProvider,
contentModal
};
},

View File

@@ -24,6 +24,7 @@
v-if="formData && formData.fields"
:form-data="formData"
:form-values="formValues"
:api-prefix="apiPrefix"
:is-submitting="isLoading"
:hide-actions="true"
@update:form-values="updateFormValues"

View File

@@ -19,6 +19,7 @@
<dynamic-form
:form-data="message.formData"
:form-values="message.formValues"
:api-prefix="apiPrefix"
:read-only="true"
hide-actions
class="message-form user-form"

View 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>

View File

@@ -2,7 +2,7 @@
<div class="dynamic-form-container">
<div class="dynamic-form" :class="{ 'readonly': readOnly, 'edit': !readOnly }">
<!-- Form header with icon and title -->
<div v-if="formData.title || formData.name || formData.icon" class="form-header">
<div v-if="shouldShowFormHeader" class="form-header">
<div v-if="formData.icon" class="form-icon">
<span class="material-symbols-outlined">{{ formData.icon }}</span>
</div>
@@ -19,6 +19,8 @@
:field-id="field.id || field.name"
:model-value="localFormValues[field.id || field.name]"
@update:model-value="updateFieldValue(field.id || field.name, $event)"
@open-privacy-modal="openPrivacyModal"
@open-terms-modal="openTermsModal"
/>
</template>
<template v-else-if="typeof formData.fields === 'object'">
@@ -29,6 +31,8 @@
:field-id="fieldId"
:model-value="localFormValues[fieldId]"
@update:model-value="updateFieldValue(fieldId, $event)"
@open-privacy-modal="openPrivacyModal"
@open-terms-modal="openTermsModal"
/>
</template>
</div>
@@ -68,12 +72,14 @@
</div>
</div>
</div>
</div>
</template>
<script>
import FormField from './FormField.vue';
import { useIconManager } from '../js/composables/useIconManager.js';
import { injectContentModal } from '../js/composables/useContentModal.js';
export default {
name: 'DynamicForm',
@@ -82,11 +88,14 @@ export default {
},
setup(props) {
const { watchIcon } = useIconManager();
const contentModal = injectContentModal();
// Watch formData.icon for automatic icon loading
watchIcon(() => props.formData?.icon);
return {};
return {
contentModal
};
},
props: {
formData: {
@@ -136,6 +145,10 @@ export default {
hideActions: {
type: Boolean,
default: false
},
apiPrefix: {
type: String,
required: true
}
},
emits: ['submit', 'cancel', 'update:formValues'],
@@ -195,6 +208,16 @@ export default {
}
return missingFields.length === 0;
},
// Title display mode configuration
titleDisplayMode() {
console.log('Title display mode:', window.chatConfig?.form_title_display || 'Full Title');
return window.chatConfig?.form_title_display || 'Full Title';
},
// Determine if form header should be shown
shouldShowFormHeader() {
const hasContent = this.formData.title || this.formData.name || this.formData.icon;
return hasContent && this.titleDisplayMode !== 'No Title';
}
},
watch: {
@@ -393,6 +416,40 @@ export default {
}
return value.toString();
},
// Modal handling methods
openPrivacyModal() {
this.loadContent('privacy');
},
openTermsModal() {
this.loadContent('terms');
},
closeModal() {
this.contentModal.hideModal();
},
retryLoad() {
// Retry loading the last requested content type
const currentTitle = this.contentModal.modalState.title.toLowerCase();
if (currentTitle.includes('privacy')) {
this.loadContent('privacy');
} else if (currentTitle.includes('terms')) {
this.loadContent('terms');
}
},
async loadContent(contentType) {
const title = contentType === 'privacy' ? 'Privacy Statement' : 'Terms & Conditions';
const contentUrl = `${this.apiPrefix}/${contentType}`;
// Use the composable to show modal and load content
await this.contentModal.showModal({
title: title,
contentUrl: contentUrl
});
}
}
};

View File

@@ -99,7 +99,16 @@
:required="field.required"
style="margin-right: 8px;"
>
<span class="checkbox-text">{{ field.name }}</span>
<!-- Regular checkbox label -->
<span v-if="!isConsentField" class="checkbox-text">{{ field.name }}</span>
<!-- Consent field with privacy and terms links -->
<span v-else class="checkbox-text consent-text">
{{ texts.consentPrefix }}
<a href="#" @click="openPrivacyModal" class="consent-link">{{ texts.privacyLink }}</a>
{{ texts.consentMiddle }}
<a href="#" @click="openTermsModal" class="consent-link">{{ texts.termsLink }}</a>
{{ texts.consentSuffix }}
</span>
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
</label>
</div>
@@ -167,6 +176,8 @@
</template>
<script>
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
export default {
name: 'FormField',
props: {
@@ -185,8 +196,57 @@ export default {
default: null
}
},
emits: ['update:modelValue'],
emits: ['update:modelValue', 'open-privacy-modal', 'open-terms-modal'],
setup() {
// Consent text constants (English base)
const consentTexts = {
consentPrefix: "I agree with the",
consentMiddle: "and",
consentSuffix: "of AskEveAI",
privacyLink: "privacy statement",
termsLink: "terms and conditions"
};
try {
// Initialize translations for this component
const { texts } = useComponentTranslations('FormField', consentTexts);
return {
translatedTexts: texts,
fallbackTexts: consentTexts
};
} catch (error) {
console.error('FormField setup(): Error in useComponentTranslations:', error);
// Return fallback texts if LanguageProvider fails
return {
translatedTexts: null,
fallbackTexts: consentTexts
};
}
},
computed: {
texts() {
// Robust consent texts that always return valid values
// Use translated texts if available and valid, otherwise use fallback
if (this.translatedTexts && typeof this.translatedTexts === 'object') {
const translated = this.translatedTexts;
// Check if translated texts have all required properties
if (translated.consentPrefix && translated.consentMiddle && translated.consentSuffix &&
translated.privacyLink && translated.termsLink) {
return translated;
}
}
// Fallback to English texts
return this.fallbackTexts || {
consentPrefix: "I agree with the",
consentMiddle: "and",
consentSuffix: "of AskEveAI",
privacyLink: "privacy statement",
termsLink: "terms and conditions"
};
},
value: {
get() {
// Gebruik default waarde als modelValue undefined is
@@ -239,6 +299,12 @@ export default {
},
description() {
return this.field.description || '';
},
isConsentField() {
// Detect consent fields by fieldId (key in dictionary) only, not by name (translated label)
return this.field.type === 'boolean' &&
(this.fieldId === 'consent' ||
this.fieldId.toLowerCase().includes('consent'));
}
},
methods: {
@@ -247,6 +313,14 @@ export default {
if (file) {
this.value = file;
}
},
openPrivacyModal(event) {
event.preventDefault();
this.$emit('open-privacy-modal');
},
openTermsModal(event) {
event.preventDefault();
this.$emit('open-terms-modal');
}
}
};
@@ -399,6 +473,29 @@ export default {
margin-left: 2px;
}
/* Consent field styling */
.consent-text {
line-height: 1.4;
}
.consent-link {
color: #007bff;
text-decoration: underline;
cursor: pointer;
transition: color 0.2s ease;
}
.consent-link:hover {
color: #0056b3;
text-decoration: underline;
}
.consent-link:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
border-radius: 2px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.form-field {

View File

@@ -34,17 +34,17 @@ const props = defineProps({
}
});
// Use component translations from provider
const originalTexts = computed(() => ({
// Use component translations from provider - fix: use reactive object instead of computed
const originalTexts = {
explanation: props.originalText || ''
}));
};
const { translations, isLoading, error, currentLanguage } = useComponentTranslations(
const { texts: translations, isLoading, error, currentLanguage } = useComponentTranslations(
'sidebar_explanation',
originalTexts.value
originalTexts
);
const translatedText = computed(() => translations.value.explanation || props.originalText);
const translatedText = computed(() => translations.value?.explanation || props.originalText);
// Render markdown content
const renderedExplanation = computed(() => {
@@ -61,12 +61,11 @@ const renderedExplanation = computed(() => {
});
// Watch for text changes to update the provider
watch(() => props.originalText, () => {
// Update original texts when prop changes
originalTexts.value = {
explanation: props.originalText || ''
};
});
watch(() => props.originalText, (newText) => {
// Re-register component with new text if needed
// The LanguageProvider will handle the update automatically
console.log('SideBarExplanation: Original text changed to:', newText);
}, { immediate: true });
</script>
<style scoped>

View File

@@ -11,6 +11,7 @@
window.chatConfig = {
explanation: `{{ customisation.sidebar_markdown|default('') }}`,
progress_tracker_insights: `{{ customisation.progress_tracker_insights|default('No Information') }}`,
form_title_display: `{{ customisation.form_title_display|default('Full Title') }}`,
conversationId: '{{ conversation_id|default("default") }}',
messages: {{ messages|tojson|safe }},
settings: {

View File

@@ -3,7 +3,7 @@ import uuid
from flask import Blueprint, render_template, request, session, current_app, jsonify, Response, stream_with_context
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.extensions import db, content_manager
from common.models.user import Tenant, SpecialistMagicLinkTenant, TenantMake
from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction
from common.services.interaction.specialist_services import SpecialistServices
@@ -378,3 +378,70 @@ def translate():
'error': f"Error translating: {str(e)}"
}), 500
@chat_bp.route('/privacy', methods=['GET'])
def privacy_statement():
"""
Public AJAX endpoint for privacy statement content
Returns JSON response suitable for modal display
"""
try:
# Use content_manager to get the latest privacy content
content_data = content_manager.read_content('privacy')
if not content_data:
current_app.logger.error("Privacy statement content not found")
return jsonify({
'error': 'Privacy statement not available',
'message': 'The privacy statement could not be loaded at this time.'
}), 404
current_app.logger.debug(f"Content data: {content_data}")
# Return JSON response for AJAX consumption
return jsonify({
'title': 'Privacy Statement',
'content': content_data['content'],
'version': content_data['version'],
'content_type': content_data['content_type']
}), 200
except Exception as e:
current_app.logger.error(f"Error loading privacy statement: {str(e)}")
return jsonify({
'error': 'Server error',
'message': 'An error occurred while loading the privacy statement.'
}), 500
@chat_bp.route('/terms', methods=['GET'])
def terms_conditions():
"""
Public AJAX endpoint for terms & conditions content
Returns JSON response suitable for modal display
"""
try:
# Use content_manager to get the latest terms content
content_data = content_manager.read_content('terms')
if not content_data:
current_app.logger.error("Terms & conditions content not found")
return jsonify({
'error': 'Terms & conditions not available',
'message': 'The terms & conditions could not be loaded at this time.'
}), 404
# Return JSON response for AJAX consumption
return jsonify({
'title': 'Terms & Conditions',
'content': content_data['content'],
'version': content_data['version'],
'content_type': content_data['content_type']
}), 200
except Exception as e:
current_app.logger.error(f"Error loading terms & conditions: {str(e)}")
return jsonify({
'error': 'Server error',
'message': 'An error occurred while loading the terms & conditions.'
}), 500

View File

@@ -168,7 +168,7 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
# We are in orientation mode, so we give a standard message, and move to rag state
start_selection_question = TranslationServices.translate(self.tenant_id, START_SELECTION_QUESTION,
arguments.language)
self.flow.state.answer = f"{welcome_message}\n\n{start_selection_question}"
self.flow.state.answer = f"{welcome_message}"
self.flow.state.phase = "rag"
results = SelectionResult.create_for_type(self.type, self.type_version)

View File

@@ -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>

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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())

View File

@@ -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!');