From b4e58659a80c0e4e09fe77c98824c5dc6ab7017e Mon Sep 17 00:00:00 2001 From: Josako Date: Tue, 3 Jun 2025 09:48:50 +0200 Subject: [PATCH] - Allow and improve viewing of different content types. First type implemented: changelog --- common/extensions.py | 2 + common/utils/content_utils.py | 215 +++++++++++++ config/config.py | 3 + .../changelog/1.0/1.0.0.md | 0 content/privacy/1.0/1.0.0.md | 37 +++ content/terms/1.0/1.0.0.md | 37 +++ docker/compose_dev.yaml | 1 + docker/eveai_app/Dockerfile | 1 + ...eai Chat Client Developer Documentation.md | 296 ++++++++++++++++++ eveai_app/__init__.py | 3 +- eveai_app/templates/basic/view_markdown.html | 102 ++++++ eveai_app/views/basic_views.py | 67 +++- 12 files changed, 748 insertions(+), 16 deletions(-) create mode 100644 common/utils/content_utils.py rename nginx/static/docs/CHANGELOG.md => content/changelog/1.0/1.0.0.md (100%) create mode 100644 content/privacy/1.0/1.0.0.md create mode 100644 content/terms/1.0/1.0.0.md create mode 100644 documentation/Eveai Chat Client Developer Documentation.md create mode 100644 eveai_app/templates/basic/view_markdown.html diff --git a/common/extensions.py b/common/extensions.py index 16941eb..a94f1d4 100644 --- a/common/extensions.py +++ b/common/extensions.py @@ -11,6 +11,7 @@ from flask_restx import Api from prometheus_flask_exporter import PrometheusMetrics from .utils.cache.eveai_cache_manager import EveAICacheManager +from .utils.content_utils import ContentManager from .utils.simple_encryption import SimpleEncryption from .utils.minio_utils import MinioClient @@ -30,4 +31,5 @@ simple_encryption = SimpleEncryption() minio_client = MinioClient() metrics = PrometheusMetrics.for_app_factory() cache_manager = EveAICacheManager() +content_manager = ContentManager() diff --git a/common/utils/content_utils.py b/common/utils/content_utils.py new file mode 100644 index 0000000..48ec29c --- /dev/null +++ b/common/utils/content_utils.py @@ -0,0 +1,215 @@ +import os +import re +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 + if app: + self.init_app(app) + + def init_app(self, app): + 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']}") + + def get_content_path(self, content_type, major_minor=None, patch=None): + """ + Geef het volledige pad naar een contentbestand + + Args: + content_type (str): Type content (bv. 'changelog', 'terms') + major_minor (str, optional): Major.Minor versie (bv. '1.0') + patch (str, optional): Patchnummer (bv. '5') + + Returns: + str: Volledige pad naar de content map of bestand + """ + content_path = os.path.join(self.app.config['CONTENT_DIR'], content_type) + + if major_minor: + content_path = os.path.join(content_path, major_minor) + + if patch: + content_path = os.path.join(content_path, f"{major_minor}.{patch}.md") + + return content_path + + def _parse_version(self, filename): + """Parse een versienummer uit een bestandsnaam""" + match = re.match(r'(\d+\.\d+)\.(\d+)\.md', filename) + if match: + return match.group(1), match.group(2) + return None, None + + def get_latest_version(self, content_type, major_minor=None): + """ + Verkrijg de laatste versie van een bepaald contenttype + + Args: + content_type (str): Type content (bv. 'changelog', 'terms') + major_minor (str, optional): Specifieke major.minor versie, anders de hoogste + + Returns: + tuple: (major_minor, patch, full_version) of None als niet gevonden + """ + try: + # Basispad voor dit contenttype + 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}") + return None + + # Als geen major_minor opgegeven, vind de hoogste + if not major_minor: + available_versions = os.listdir(content_path) + if not available_versions: + return None + + # Sorteer op versienummer (major.minor) + available_versions.sort(key=lambda v: version.parse(v)) + major_minor = available_versions[-1] + + # Nu we major_minor hebben, zoek de hoogste patch + major_minor_path = os.path.join(content_path, major_minor) + + if not os.path.exists(major_minor_path): + logger.error(f"Version path does not exist: {major_minor_path}") + return None + + files = os.listdir(major_minor_path) + version_files = [] + + for file in files: + mm, p = self._parse_version(file) + if mm == major_minor and p: + version_files.append((mm, p, f"{mm}.{p}")) + + if not version_files: + return None + + # Sorteer op patch nummer + version_files.sort(key=lambda v: int(v[1])) + return version_files[-1] + + except Exception as e: + 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): + """ + Lees content met versieondersteuning + + Als major_minor en patch niet zijn opgegeven, wordt de laatste versie gebruikt. + Als alleen major_minor is opgegeven, wordt de laatste patch van die versie gebruikt. + + Args: + content_type (str): Type content (bv. 'changelog', 'terms') + major_minor (str, optional): Major.Minor versie (bv. '1.0') + patch (str, optional): Patchnummer (bv. '5') + + Returns: + dict: { + 'content': str, + 'version': str, + 'content_type': str + } of None bij fout + """ + try: + # 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}") + return None + + major_minor, patch, full_version = version_info + + # Als geen patch opgegeven, vind de laatste patch voor deze major_minor + 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}") + return None + + major_minor, patch, full_version = version_info + else: + full_version = f"{major_minor}.{patch}" + + # Nu hebben we major_minor en patch, lees het bestand + file_path = self.get_content_path(content_type, major_minor, patch) + + if not os.path.exists(file_path): + 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() + + return { + 'content': content, + 'version': full_version, + 'content_type': content_type + } + + except Exception as e: + logger.error(f"Error reading content {content_type} {major_minor}.{patch}: {str(e)}") + return None + + def list_content_types(self): + """Lijst alle beschikbare contenttypes op""" + try: + 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)}") + return [] + + def list_versions(self, content_type): + """ + Lijst alle beschikbare versies voor een contenttype + + Returns: + list: Lijst van dicts met versie-informatie + [{'version': '1.0.0', 'path': '/path/to/file', 'date_modified': datetime}] + """ + versions = [] + try: + content_path = os.path.join(self.app.config['CONTENT_DIR'], content_type) + + if not os.path.exists(content_path): + return [] + + for major_minor in os.listdir(content_path): + major_minor_path = os.path.join(content_path, major_minor) + + if not os.path.isdir(major_minor_path): + continue + + for file in os.listdir(major_minor_path): + mm, p = self._parse_version(file) + if mm and p: + file_path = os.path.join(major_minor_path, file) + mod_time = os.path.getmtime(file_path) + versions.append({ + 'version': f"{mm}.{p}", + 'path': file_path, + 'date_modified': mod_time + }) + + # Sorteer op versienummer + versions.sort(key=lambda v: version.parse(v['version'])) + return versions + + except Exception as e: + logger.error(f"Error listing versions for {content_type}: {str(e)}") + return [] diff --git a/config/config.py b/config/config.py index 903bd9b..cde13f1 100644 --- a/config/config.py +++ b/config/config.py @@ -172,6 +172,9 @@ class Config(object): # Entitlement Constants ENTITLEMENTS_MAX_PENDING_DAYS = 5 # Defines the maximum number of days a pending entitlement can be active + # Content Directory for static content like the changelog, terms & conditions, privacy statement, ... + CONTENT_DIR = '/app/content' + class DevConfig(Config): DEVELOPMENT = True diff --git a/nginx/static/docs/CHANGELOG.md b/content/changelog/1.0/1.0.0.md similarity index 100% rename from nginx/static/docs/CHANGELOG.md rename to content/changelog/1.0/1.0.0.md diff --git a/content/privacy/1.0/1.0.0.md b/content/privacy/1.0/1.0.0.md new file mode 100644 index 0000000..5e5f5e9 --- /dev/null +++ b/content/privacy/1.0/1.0.0.md @@ -0,0 +1,37 @@ +# Privacy Policy + +## Version 1.0.0 + +*Effective Date: 2025-06-03* + +### 1. Introduction + +This Privacy Policy describes how EveAI collects, uses, and discloses your information when you use our services. + +### 2. Information We Collect + +We collect information you provide directly to us, such as account information, content you process through our services, and communication data. + +### 3. How We Use Your Information + +We use your information to provide, maintain, and improve our services, process transactions, send communications, and comply with legal obligations. + +### 4. Data Security + +We implement appropriate security measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction. + +### 5. International Data Transfers + +Your information may be transferred to and processed in countries other than the country you reside in, where data protection laws may differ. + +### 6. Your Rights + +Depending on your location, you may have certain rights regarding your personal information, such as access, correction, deletion, or restriction of processing. + +### 7. Changes to This Policy + +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. + +### 8. Contact Us + +If you have any questions about this Privacy Policy, please contact us at privacy@askeveai.be. diff --git a/content/terms/1.0/1.0.0.md b/content/terms/1.0/1.0.0.md new file mode 100644 index 0000000..95cb4aa --- /dev/null +++ b/content/terms/1.0/1.0.0.md @@ -0,0 +1,37 @@ +# Terms of Service + +## Version 1.0.0 + +*Effective Date: 2025-06-03* + +### 1. Introduction + +Welcome to EveAI. By accessing or using our services, you agree to be bound by these Terms of Service. + +### 2. Service Description + +EveAI provides AI-powered solutions for businesses to optimize their operations through intelligent document processing and specialist execution. + +### 3. User Accounts + +To access certain features of the Service, you must register for an account. You are responsible for maintaining the confidentiality of your account information. + +### 4. Privacy + +Your use of the Service is also governed by our Privacy Policy, which can be found [here](/content/privacy). + +### 5. Intellectual Property + +All content, features, and functionality of the Service are owned by EveAI and are protected by international copyright, trademark, and other intellectual property laws. + +### 6. Limitation of Liability + +In no event shall EveAI be liable for any indirect, incidental, special, consequential or punitive damages. + +### 7. Changes to Terms + +We reserve the right to modify these Terms at any time. Your continued use of the Service after such modifications will constitute your acceptance of the new Terms. + +### 8. Governing Law + +These Terms shall be governed by the laws of Belgium. diff --git a/docker/compose_dev.yaml b/docker/compose_dev.yaml index 2bc6566..4bc4ac8 100644 --- a/docker/compose_dev.yaml +++ b/docker/compose_dev.yaml @@ -91,6 +91,7 @@ services: volumes: - ../eveai_app:/app/eveai_app - ../common:/app/common + - ../content:/app/content - ../config:/app/config - ../migrations:/app/migrations - ../scripts:/app/scripts diff --git a/docker/eveai_app/Dockerfile b/docker/eveai_app/Dockerfile index 860651c..f302fff 100644 --- a/docker/eveai_app/Dockerfile +++ b/docker/eveai_app/Dockerfile @@ -56,6 +56,7 @@ COPY config /app/config COPY migrations /app/migrations COPY scripts /app/scripts COPY patched_packages /app/patched_packages +COPY content /app/content # Set permissions for entrypoint script RUN chmod 777 /app/scripts/entrypoint.sh diff --git a/documentation/Eveai Chat Client Developer Documentation.md b/documentation/Eveai Chat Client Developer Documentation.md new file mode 100644 index 0000000..8a2c63e --- /dev/null +++ b/documentation/Eveai Chat Client Developer Documentation.md @@ -0,0 +1,296 @@ +# Evie Chat Client - Developer Documentation + +## Overview + +The Evie Chat Client is a modern, customizable chat interface for interacting with eveai specialists. It supports both anonymous and authenticated modes, with initial focus on anonymous mode. The client provides real-time interaction with AI specialists, customizable tenant branding, and European-compliant analytics tracking. + +## Key Features + +- **Anonymous Mode**: Public access with tenant UUID and API key authentication +- **Real-time Communication**: Server-Sent Events (SSE) for live updates and intermediate states +- **Tenant Customization**: Simple CSS variable-based theming with visual editor +- **Multiple Choice Options**: Dynamic button/dropdown responses from specialists +- **Chat History**: Persistent ChatSession and Interaction storage +- **File Upload Support**: Planned for future implementation +- **European Analytics**: Umami integration for GDPR-compliant tracking + +## Architecture + +### Component Structure + +``` +eveai_chat_client/ +├── app.py # Flask app entry point +├── routes/ +│ ├── __init__.py +│ ├── chat_routes.py # Main chat interface routes +│ └── api_routes.py # SSE/API endpoints +├── services/ +│ ├── chat_service.py # Chat session management +│ ├── specialist_service.py # Specialist interaction wrapper +│ └── tenant_service.py # Tenant config & theming +├── templates/ +│ ├── base.html # Base template +│ ├── chat.html # Main chat interface +│ └── components/ +│ ├── message.html # Individual message component +│ ├── options.html # Multiple choice options +│ └── thinking.html # Intermediate states display +└── utils/ + ├── auth.py # API key validation + └── tracking.py # Umami analytics integration +``` + +### Integration Approach + +- **Services Layer**: Direct integration with existing eveai services (not API) for better performance +- **Database**: Utilizes existing ChatSession and Interaction models +- **Caching**: Leverages existing Redis setup +- **Static Files**: Uses existing nginx/static structure + +## URL Structure & Parameters + +### Main Chat Interface +``` +GET /chat/{tenant_code}/{specialist_id} +``` + +**Query Parameters:** +- `api_key` (required): Tenant API key for authentication +- `utm_source`, `utm_campaign`, `utm_medium` (optional): Analytics tracking +- Other tracking parameters as needed + +**Example:** +``` +/chat/550e8400-e29b-41d4-a716-446655440000/document-analyzer?api_key=xxx&utm_source=email +``` + +### API Endpoints +``` +POST /api/chat/{tenant_code}/interact # Send message to specialist +GET /api/chat/{tenant_code}/status/{session_id} # SSE endpoint for updates +GET /api/tenant/{tenant_code}/theme.css # Dynamic tenant CSS (if needed) +``` + +## Authentication & Security + +### Anonymous Mode +- **Tenant Identification**: UUID-based tenant codes (not sequential IDs) +- **API Key Validation**: Required for all anonymous interactions +- **Rate Limiting**: Implement per-tenant/IP rate limiting +- **Input Validation**: Sanitize all user inputs and parameters + +### Security Considerations +- Use tenant UUIDs to prevent enumeration attacks +- Validate API keys against tenant database +- Implement CORS policies for cross-origin requests +- Sanitize all user messages and file uploads + +## Real-time Communication + +### Server-Sent Events (SSE) +- **Connection**: Long-lived SSE connection per chat session +- **Message Types**: + - `message`: Complete specialist response + - `thinking`: Intermediate processing states + - `options`: Multiple choice response options + - `error`: Error messages + - `complete`: Interaction completion + +### SSE Message Format +```json +{ + "type": "thinking", + "data": { + "message": "Analyzing your request...", + "step": 1, + "total_steps": 3 + } +} +``` + +## Tenant Customization + +### Theme Configuration +Stored in tenant table as JSONB column: +```sql +ALTER TABLE tenants ADD COLUMN theme_config JSONB; +``` + +### CSS Variables Approach +Inline CSS variables in chat template: +```css +:root { + /* Brand Colors */ + --primary-color: {{ tenant.theme_config.primary_color or '#007bff' }}; + --secondary-color: {{ tenant.theme_config.secondary_color or '#6c757d' }}; + --accent-color: {{ tenant.theme_config.accent_color or '#28a745' }}; + + /* Chat Interface */ + --user-message-bg: {{ tenant.theme_config.user_message_bg or 'var(--primary-color)' }}; + --bot-message-bg: {{ tenant.theme_config.bot_message_bg or '#f8f9fa' }}; + --chat-bg: {{ tenant.theme_config.chat_bg or '#ffffff' }}; + + /* Typography */ + --font-family: {{ tenant.theme_config.font_family or 'system-ui, -apple-system, sans-serif' }}; + --font-size-base: {{ tenant.theme_config.font_size or '16px' }}; + + /* Branding */ + --logo-url: url('/api/tenant/{{ tenant.code }}/logo'); + --header-bg: {{ tenant.theme_config.header_bg or 'var(--primary-color)' }}; +} +``` + +### Theme Editor (eveai_app) +Simple form interface with: +- Color pickers for brand colors +- Font selection dropdown +- Logo upload functionality +- Live preview of chat interface +- Reset to defaults option + +## Multiple Choice Options + +### Dynamic Rendering Logic +```python +def render_options(options_list): + if len(options_list) <= 3: + return render_template('components/options.html', + display_type='buttons', + options=options_list) + else: + return render_template('components/options.html', + display_type='dropdown', + options=options_list) +``` + +### Option Data Structure +```json +{ + "type": "options", + "data": { + "question": "How would you like to proceed?", + "options": [ + {"id": "option1", "text": "Continue analysis", "value": "continue"}, + {"id": "option2", "text": "Generate report", "value": "report"}, + {"id": "option3", "text": "Start over", "value": "restart"} + ] + } +} +``` + +## Analytics Integration + +### Umami Setup +- **European Hosting**: Self-hosted Umami instance +- **Privacy Compliant**: No cookies, GDPR compliant by design +- **Tracking Events**: + - Chat session start + - Message sent + - Option selected + - Session duration + - Specialist interaction completion + +### Tracking Implementation +```javascript +// Track chat events +function trackEvent(eventName, eventData) { + if (window.umami) { + umami.track(eventName, eventData); + } +} +``` + +## File Upload Support (Future) + +### Planned Implementation +- **Multipart Upload**: Standard HTML5 file upload +- **File Types**: Documents, images, spreadsheets +- **Storage**: Tenant-specific S3 buckets +- **Processing**: Integration with existing document processing pipeline +- **UI**: Drag-and-drop interface with progress indicators + +### Security Considerations +- File type validation +- Size limits per tenant +- Virus scanning integration +- Temporary file cleanup + +## Development Guidelines + +### Code Organization +- Follow existing eveai project structure and conventions +- Use existing common/services and common/utils where applicable +- Maintain multi-tenant data isolation +- Implement proper error handling and logging + +### Testing Strategy +- Unit tests for services and utilities +- Integration tests for chat flow +- UI tests for theme customization +- Load testing for SSE connections +- Cross-browser compatibility testing + +### Performance Considerations +- Cache tenant configurations in Redis +- Optimize SSE connection management +- Implement connection pooling for database +- Use CDN for static assets +- Monitor real-time connection limits + +## Deployment + +### Container Configuration +- New `eveai_chat_client` container +- Integration with existing docker setup +- Environment configuration for tenant isolation +- Load balancer configuration for SSE connections + +### Dependencies +- Flask and Flask-restx (existing) +- Celery integration (existing) +- PostgreSQL and Redis (existing) +- Umami analytics client library + +## Future Enhancements + +### Authenticated Mode +- User login integration +- Session persistence across devices +- Advanced specialist access controls +- User-specific chat history + +### Advanced Features +- Voice message support +- Screen sharing capabilities +- Collaborative chat sessions +- Advanced analytics dashboard +- Mobile app integration + +## Configuration Examples + +### Environment Variables +```bash +CHAT_CLIENT_PORT=5000 +TENANT_API_VALIDATION_CACHE_TTL=3600 +SSE_CONNECTION_TIMEOUT=300 +UMAMI_WEBSITE_ID=your-website-id +UMAMI_SCRIPT_URL=https://your-umami.domain/script.js +``` + +### Sample Theme Configuration +```json +{ + "primary_color": "#2563eb", + "secondary_color": "#64748b", + "accent_color": "#059669", + "user_message_bg": "#2563eb", + "bot_message_bg": "#f1f5f9", + "chat_bg": "#ffffff", + "font_family": "Inter, system-ui, sans-serif", + "font_size": "16px", + "header_bg": "#1e40af" +} +``` + +This documentation provides a comprehensive foundation for developing the Evie Chat Client while maintaining consistency with the existing eveai architecture and meeting the specific requirements for anonymous mode interactions with customizable tenant branding. \ No newline at end of file diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 55ac192..aee03c0 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -7,7 +7,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix import logging.config from common.extensions import (db, migrate, bootstrap, security, login_manager, cors, csrf, session, - minio_client, simple_encryption, metrics, cache_manager) + minio_client, simple_encryption, metrics, cache_manager, content_manager) from common.models.user import User, Role, Tenant, TenantDomain import common.models.interaction import common.models.entitlements @@ -124,6 +124,7 @@ def register_extensions(app): minio_client.init_app(app) cache_manager.init_app(app) metrics.init_app(app) + content_manager.init_app(app) def register_blueprints(app): diff --git a/eveai_app/templates/basic/view_markdown.html b/eveai_app/templates/basic/view_markdown.html new file mode 100644 index 0000000..c002c24 --- /dev/null +++ b/eveai_app/templates/basic/view_markdown.html @@ -0,0 +1,102 @@ +{% extends "base.html" %} +{% block title %}{{ title }}{% endblock %} + +{% block content_title %}{{ title }}{% endblock %} +{% block content_description %}{{ description }}{% endblock %} + +{% block content %} +
+
+
+
+ + +
+
+
+ + + + +
+ {{ markdown_content | markdown }} +
+
+
+
+{% endblock %} + +{% block styles %} +{{ super() }} + + +{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/eveai_app/views/basic_views.py b/eveai_app/views/basic_views.py index 6e0302c..3f91cef 100644 --- a/eveai_app/views/basic_views.py +++ b/eveai_app/views/basic_views.py @@ -9,9 +9,17 @@ from common.models.user import Tenant from common.utils.database import Database from common.utils.nginx_utils import prefixed_url_for from .basic_forms import SessionDefaultsForm +from common.extensions import content_manager + +import markdown basic_bp = Blueprint('basic_bp', __name__) +# Markdown filter toevoegen aan Jinja2 +@basic_bp.app_template_filter('markdown') +def render_markdown(text): + return markdown.markdown(text, extensions=['tables', 'fenced_code']) + @basic_bp.before_request def log_before_request(): @@ -104,28 +112,57 @@ def check_csrf(): }) -@basic_bp.route('/release_notes', methods=['GET']) +@basic_bp.route('/content/', methods=['GET']) @roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') -def release_notes(): - """Display the CHANGELOG.md file.""" +def view_content(content_type): + """ + Show content like release notes, terms of use, etc. + + Args: + content_type (str): Type content (eg. 'changelog', 'terms', 'privacy') + """ try: - # Construct the URL to the CHANGELOG.md file in the static directory - static_url = url_for('static', filename='docs/CHANGELOG.md', _external=True) + current_app.logger.debug(f"Showing content {content_type}") + major_minor = request.args.get('version') + patch = request.args.get('patch') - # Make a request to get the content of the CHANGELOG.md file - response = requests.get(static_url) - response.raise_for_status() # Raise an exception for HTTP errors + # Gebruik de ContentManager om de content op te halen + content_data = content_manager.read_content(content_type, major_minor, patch) - # Get the content of the response - markdown_content = response.text + if not content_data: + flash(f'Content van type {content_type} werd niet gevonden.', 'danger') + return redirect(prefixed_url_for('basic_bp.index')) + + # Titels en beschrijvingen per contenttype + titles = { + 'changelog': 'Release Notes', + 'terms': 'Terms & Conditions', + 'privacy': 'Privacy Statement', + # Voeg andere types toe indien nodig + } + + descriptions = { + 'changelog': 'EveAI Release Notes', + 'terms': "Terms & Conditions for using AskEveAI's Evie", + 'privacy': "Privacy Statement for AskEveAI's Evie", + # Voeg andere types toe indien nodig + } return render_template( 'basic/view_markdown.html', - title='Release Notes', - description='EveAI Release Notes and Change History', - markdown_content=markdown_content + title=titles.get(content_type, content_type.capitalize()), + description=descriptions.get(content_type, ''), + markdown_content=content_data['content'], + version=content_data['version'] ) except Exception as e: - current_app.logger.error(f"Error displaying release notes: {str(e)}") - flash(f'Error displaying release notes: {str(e)}', 'danger') + current_app.logger.error(f"Error displaying content {content_type}: {str(e)}") + flash(f'Error displaying content: {str(e)}', 'danger') return redirect(prefixed_url_for('basic_bp.index')) + +@basic_bp.route('/release_notes', methods=['GET']) +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def release_notes(): + """Doorverwijzen naar de nieuwe content view voor changelog""" + current_app.logger.debug(f"Redirecting to content viewer") + return redirect(prefixed_url_for('basic_bp.view_content', content_type='changelog'))